Pacchetto Rust con una libreria e un binario?


190

Vorrei creare un pacchetto Rust che contenga sia una libreria riutilizzabile (dove viene implementata la maggior parte del programma), sia un eseguibile che lo utilizza.

Supponendo che non ho confuso alcuna semantica nel sistema del modulo Rust, come dovrebbe essere il mio Cargo.tomlfile?

Risposte:


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
Grazie Doug, ci proverò! Le annotazioni #! [Crate_name =] e #! [Crate_type] sono opzionali allora?
Andrew Wagner,

4
Quando si utilizza Cargo, queste opzioni non sono necessarie perché Cargo le passa come flag del compilatore. Se corri cargo build --verbose, li vedrai nella rustcriga di comando.
Vladimir Matveev,

33
Sai perché [[bin]]è un array di tabelle? Perché usare [[bin]]e no [bin]? Non sembra esserci alcuna documentazione al riguardo.
CMCDragonkai,

40
@CMCDragonkai È la specifica del formato toml [[x]] è un array una volta deserializzato; vale a dire. una singola cassa può produrre più binari, ma solo una libreria (quindi [lib], non [[lib]]). Puoi avere più sezioni bin. (Sono d'accordo, sembra strano, ma Toml è sempre stata una scelta controversa).
Doug,

1
C'è un modo per impedire che compili il binario quando tutto ciò che voglio è la lib? Il binario ha dipendenze aggiuntive che aggiungo tramite una funzione chiamata "binario", quando provo a compilarlo senza quella funzione, non riesce a costruirlo. Si lamenta che non riesce a trovare le casse che bin.rs sta cercando di importare.
Person93

150

Puoi anche inserire semplicemente le fonti binarie src/bine il resto delle tue fonti src. Puoi vedere un esempio nel mio progetto . Non è necessario modificarlo Cargo.tomlaffatto e ogni file di origine verrà compilato in un file binario con lo stesso nome.

La configurazione dell'altra risposta viene quindi sostituita da:

$ 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));
}

Ed eseguilo:

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

Inoltre, puoi semplicemente creare un file src/main.rsche verrà utilizzato come eseguibile defacto. Sfortunatamente, questo è in conflitto con il cargo doccomando:

Impossibile documentare un pacchetto in cui una libreria e un file binario hanno lo stesso nome. Valuta di rinominarne uno o di contrassegnare il target comedoc = false


13
si adatta bene all'approccio con convenzione di over-configuration di rust! entrambi rispondono insieme e tu hai una grande comodità e flessibilità.
pecora volante

9
extern crate example;non è richiesto a partire da rust 2018, puoi scrivere use example::really_complicated_code;e utilizzare direttamente la funzione senza nominare l'ambito
sassman

47

Una soluzione alternativa è quella di non provare a raggruppare entrambe le cose in un unico pacchetto. Per progetti leggermente più grandi con un eseguibile amichevole, ho trovato molto bello usare uno spazio di lavoro

Creiamo un progetto binario che include una libreria al suo interno:

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

Cargo.toml

Questo utilizza la [workspace]chiave e dipende dalla libreria:

[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)
}

Ed eseguilo:

$ 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)

Ci sono due grandi vantaggi in questo schema:

  1. Il binario ora può usare dipendenze che si applicano solo ad esso. Ad esempio, è possibile includere molte cassette per migliorare l'esperienza dell'utente, come i parser della riga di comando o la formattazione del terminale. Nessuno di questi "infetterà" la biblioteca.

  2. L'area di lavoro impedisce build ridondanti di ciascun componente. Se eseguiamo cargo buildin entrambe le directory mylibrarye the-binary, la libreria non verrà creata entrambe le volte: è condivisa tra entrambi i progetti.


Questo sembra un modo molto migliore di andare. Ovviamente sono passati anni da quando è stata posta la domanda, ma le persone continuano a lottare con l'organizzazione di grandi progetti. C'è un aspetto negativo nell'utilizzo di uno spazio di lavoro rispetto alla risposta selezionata sopra?
Jspies,

4
@Jspies il lato negativo più grande che mi viene in mente dalla parte superiore della mia testa è che ci sono alcuni strumenti che non sanno come gestire gli spazi di lavoro. Sono un po 'in un posto strano quando interagiscono con strumenti esistenti che hanno una sorta di concetto di "progetto". Personalmente tendo ad adottare un approccio continuum: inizio con tutto main.rs, poi lo divido in moduli man mano che si ingrandisce, infine divido src/binquando è solo un po 'più grande, per poi spostarmi in un'area di lavoro quando inizio a riutilizzare pesantemente la logica di base.
Shepmaster

grazie ci proverò. il mio progetto attuale ha un paio di librerie sviluppate come parte del progetto ma utilizzate anche esternamente.
Jspies

Costruisce e funziona bene, ma cargo test sembra ignorare i test unitari in lib.rs
Stein,

3
@Stein Penso che tu voglia cargo test --all
Shepmaster

18

Puoi mettere lib.rse main.rsnella cartella delle fonti insieme. Non c'è conflitto e il carico costruirà entrambe le cose.

Per risolvere il conflitto documentaion aggiungi al tuo Cargo.toml:

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

3
Questo sarebbe coperto da " Inoltre, puoi semplicemente creare un src / main.rs che verrà usato come eseguibile defacto ". nell'altra risposta, no? E il conflitto di documentazione è risolto dalla risposta accettata, giusto? Potrebbe essere necessario chiarire la tua risposta per mostrare perché questo è unico. Va bene fare riferimento alle altre risposte su cui basarsi.
Shepmaster,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.