Pakiet Rust zawierający zarówno bibliotekę, jak i plik binarny?


190

Chciałbym stworzyć pakiet Rusta zawierający zarówno bibliotekę wielokrotnego użytku (w której zaimplementowana jest większość programu), jak i plik wykonywalny, który z niej korzysta.

Zakładając, że nie pomyliłem żadnej semantyki w systemie modułów Rusta, jak powinien Cargo.tomlwyglądać mój plik?

Odpowiedzi:


205
Tok:tmp doug$ du -a

8   ./Cargo.toml
8   ./src/bin.rs
8   ./src/lib.rs
16  ./src

Cargo.toml:

[package]
name = "mything"
version = "0.0.1"
authors = ["me <me@gmail.com>"]

[lib]
name = "mylib"
path = "src/lib.rs"

[[bin]]
name = "mybin"
path = "src/bin.rs"

src / lib.rs:

pub fn test() {
    println!("Test");
}

src / bin.rs:

extern crate mylib; // not needed since Rust edition 2018

use mylib::test;

pub fn main() {
    test();
}

2
Dzięki Doug, spróbuję! Czy zatem adnotacje #! [Crate_name =] i #! [Crate_type] są opcjonalne?
Andrew Wagner

4
Gdy używasz Cargo, te opcje są niepotrzebne, ponieważ Cargo przekazuje je jako flagi kompilatora. Jeśli uruchomisz cargo build --verbose, zobaczysz je w rustcwierszu poleceń.
Vladimir Matveev

33
Czy wiesz, dlaczego [[bin]]jest tablicą tabel? Dlaczego używać, [[bin]]a nie [bin]? Wydaje się, że nie ma żadnej dokumentacji na ten temat.
CMCDragonkai,

40
@CMCDragonkai To specyfikacja formatu toml [[x]] jest tablicą po deserializacji; to znaczy. pojedyncza skrzynka może tworzyć wiele plików binarnych, ale tylko jedną bibliotekę (stąd [lib], a nie [[lib]]). Możesz mieć wiele sekcji pojemnika. (Zgadzam się, to wygląda dziwnie, ale wybór tomla zawsze był kontrowersyjny).
Doug,

1
Czy istnieje sposób, aby zapobiec kompilowaniu pliku binarnego, gdy wszystko, czego chcę, to biblioteka? Plik binarny ma dodatkowe zależności, które dodam za pomocą funkcji zwanej „binarną”. Kiedy próbuję skompilować go bez tej funkcji, nie udaje się go zbudować. Narzeka, że ​​nie może znaleźć skrzynek, które bin.rs próbuje zaimportować.
Person93

150

Możesz również po prostu umieścić źródła binarne, src/bina pozostałe źródła src. Możesz zobaczyć przykład w moim projekcie . Nie musisz Cargo.tomlw ogóle modyfikować swojego , a każdy plik źródłowy zostanie skompilowany do postaci binarnej o tej samej nazwie.

Konfiguracja drugiej odpowiedzi jest następnie zastępowana przez:

$ tree
.
├── Cargo.toml
└── src
    ├── bin
    │   └── mybin.rs
    └── lib.rs

Cargo.toml

[package]
name = "example"
version = "0.0.1"
authors = ["An Devloper <an.devloper@example.com>"]

src / lib.rs

use std::error::Error;

pub fn really_complicated_code(a: u8, b: u8) -> Result<u8, Box<Error>> {
    Ok(a + b)
}

src / bin / mybin.rs

extern crate example; // Optional in Rust 2018

fn main() {
    println!("I'm using the library: {:?}", example::really_complicated_code(1, 2));
}

I wykonaj to:

$ cargo run --bin mybin
I'm using the library: Ok(3)

Dodatkowo możesz po prostu utworzyć plik, src/main.rsktóry będzie używany jako plik wykonywalny defacto. Niestety jest to sprzeczne z cargo docpoleceniem:

Nie można udokumentować pakietu, w którym biblioteka i plik binarny mają taką samą nazwę. Rozważ zmianę nazwy jednego lub oznaczenie celu jakodoc = false


13
dobrze pasuje do podejścia rdzy polegającego na nadmiernej konfiguracji! obie odpowiedzi razem i masz dużą wygodę i elastyczność.
latające owce

9
extern crate example;Nie jest to wymagane, rdzy 2018 można bezpośrednio pisać use example::really_complicated_code;i korzystać z funkcji bez nazywania zakres
sassman

47

Alternatywnym rozwiązaniem jest nie próbowanie upychania obu rzeczy w jednym pakiecie. W przypadku nieco większych projektów z przyjaznym plikiem wykonywalnym bardzo przyjemnie jest używać obszaru roboczego

Tworzymy projekt binarny, który zawiera w sobie bibliotekę:

the-binary
├── Cargo.lock
├── Cargo.toml
├── mylibrary
│   ├── Cargo.toml
│   └── src
│       └── lib.rs
└── src
    └── main.rs

Cargo.toml

Używa [workspace]klucza i zależy od biblioteki:

[package]
name = "the-binary"
version = "0.1.0"
authors = ["An Devloper <an.devloper@example.com>"]

[workspace]

[dependencies]
mylibrary = { path = "mylibrary" }

src / main.rs

extern crate mylibrary;

fn main() {
    println!("I'm using the library: {:?}", mylibrary::really_complicated_code(1, 2));
}

mylibrary / src / lib.rs

use std::error::Error;

pub fn really_complicated_code(a: u8, b: u8) -> Result<u8, Box<Error>> {
    Ok(a + b)
}

I wykonaj to:

$ cargo run
   Compiling mylibrary v0.1.0 (file:///private/tmp/the-binary/mylibrary)
   Compiling the-binary v0.1.0 (file:///private/tmp/the-binary)
    Finished dev [unoptimized + debuginfo] target(s) in 0.73 secs
     Running `target/debug/the-binary`
I'm using the library: Ok(3)

Ten program ma dwie duże korzyści:

  1. Plik binarny może teraz używać zależności, które mają zastosowanie tylko do niego. Na przykład możesz dołączyć wiele skrzynek, aby poprawić wygodę użytkownika, takich jak parsery wiersza poleceń lub formatowanie terminala. Żadne z nich nie „zainfekuje” biblioteki.

  2. Przestrzeń robocza zapobiega zbędnym kompilacjom każdego składnika. Jeśli uruchomimy cargo buildzarówno w katalogu, jak mylibraryi the-binary, biblioteka nie zostanie zbudowana dwukrotnie - jest współdzielona między oba projekty.


Wydaje się, że jest to o wiele lepsza droga. Oczywiście minęły lata, odkąd padło pytanie, ale ludzie wciąż mają problemy z organizacją dużych projektów. Czy korzystanie z obszaru roboczego ma wady w porównaniu z wybraną powyżej odpowiedzią?
Jspies

4
@Jspies Największym minusem, jaki przychodzi mi do głowy, jest to, że istnieją narzędzia, które nie do końca wiedzą, jak radzić sobie z obszarami roboczymi. Znajdują się w dziwnym miejscu podczas interakcji z istniejącymi narzędziami, które mają jakąś koncepcję „projektu”. Osobiście mam tendencję do przyjmowania podejścia kontinuum: zaczynam od wszystkiego main.rs, co jest w środku , następnie dzielę to na moduły, gdy robi się większe, w końcu dzielę się, src/bingdy jest tylko trochę większy, a następnie przechodzę do obszaru roboczego, gdy zaczynam intensywnie ponownie wykorzystywać podstawową logikę.
Shepmaster

dzięki, dam sobie radę. mój obecny projekt ma kilka bibliotek, które są rozwijane jako część projektu, ale są również używane zewnętrznie.
Jspies

cargo testKompiluje się i działa dobrze, ale wydaje się, że ignoruje testy jednostkowe w lib.rs
Stein

3
@Stein Myślę, że chceszcargo test --all
Shepmaster

18

Możesz umieścić lib.rsi main.rsdo folderu źródeł razem. Nie ma konfliktu, a ładunek zbuduje obie rzeczy.

Aby rozwiązać konflikt dokumentacji, dodaj do Cargo.toml:

[[bin]]
name = "main"
doc = false

3
Byłoby to uwzględnione w sekcjiDodatkowo, możesz po prostu utworzyć plik src / main.rs, który będzie używany jako plik wykonywalny defacto ”. w drugiej odpowiedzi, nie? A konflikt dokumentacji rozwiązuje zaakceptowana odpowiedź, prawda? Być może będziesz musiał wyjaśnić swoją odpowiedź, aby pokazać, dlaczego jest to wyjątkowe. W porządku jest odwoływanie się do innych odpowiedzi i budowanie na nich.
Shepmaster
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.