Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Spazi di Lavoro Cargo

Nel Capitolo 12 abbiamo costruito un pacchetto che includeva un crate binario e un crate libreria. Man mano che il tuo progetto si sviluppa, potresti scoprire che il crate libreria continua a diventare più grande e vorresti dividere ulteriormente il tuo pacchetto in più crate libreria. Cargo offre una funzione chiamata spazi di lavoro (workspace) che può aiutarti a gestire più pacchetti correlati che vengono sviluppati in tandem.

Creare un Workspace

Uno spazio di lavoro è un insieme di pacchetti che condividono lo stesso Cargo.lock e la stessa directory di output. Creiamo un progetto utilizzando un workspace: useremo del codice banale in modo da poterci concentrare sulla struttura del workspace. Esistono diversi modi per strutturare uno spazio di lavoro, quindi ci limiteremo a mostrarne uno comune. Avremo un workspace contenente un binario e due librerie. Il binario, che fornirà la funzionalità principale, dipenderà dalle due librerie. Una libreria fornirà una funzione più_uno e l’altra libreria una funzione più_due. Questi tre crate faranno parte dello stesso workspace. Inizia creando una nuova cartella per lo spazio di lavoro:

Nota di traduzione: per i nomi di crate non è possibile utilizzare caratteri non-ASCII, a differenza della possibilità di utilizzo nei nomi di funzione. Si è scelto di usare quindi sia piu_uno che più_uno per rimarcare questa differenza. Fai quindi attenzione.

$ mkdir somma
$ cd somma

Successivamente, nella cartella somma, creiamo il file Cargo.toml che configurerà l’intero workspace. Questo file non avrà una sezione [package], ma inizierà con una sezione [workspace] che ci permetterà di aggiungere membri al workspace. Inoltre, esplicitiamo di voler usare l’ultima versione dell’algoritmo di risoluzione delle dipendenze di Cargo nel nostro spazio di lavoro, impostando il valore resolver a "3":

File: Cargo.toml

[workspace]
resolver = "3"

Successivamente, creeremo il crate binario sommatore eseguendo cargo new nella cartella somma:

$ cargo new sommatore
     Created binary (application) `sommatore` package
      Adding `sommatore` as member of workspace at `file:///progetti/somma`

L’esecuzione di cargo new all’interno di uno spazio di lavoro aggiunge automaticamente il pacchetto appena creato alla chiave members nella definizione [workspace] del file Cargo.toml, in questo modo:

[workspace]
resolver = "3"
members = ["sommatore"]

A questo punto, possiamo costruire l’intero workspace con cargo build. I file nella tua cartella somma dovrebbero avere questo aspetto:

somma
├── Cargo.lock
├── Cargo.toml
├── sommatore
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── target

Lo spazio di lavoro ha una cartella target al livello superiore in cui verranno inseriti gli artefatti compilati; il pacchetto sommatore non ha una propria cartella target. Anche se dovessimo eseguire cargo build dall’interno della cartella sommatore, gli artefatti compilati finirebbero comunque in somma/target piuttosto che in somma/sommatore/target. Cargo struttura la cartella target in uno spazio di lavoro in questo modo perché i crate in un workspace sono destinati a dipendere l’uno dall’altro. Se ogni crate avesse la propria cartella target, ogni crate dovrebbe ricompilare ogni altro crate nello spazio di lavoro per posizionare gli artefatti nella propria cartella target. Condividendo una cartella target, i crate possono evitare inutili ricostruzioni.

Creare un Secondo Pacchetto nel Workspace

Ora creiamo un altro pacchetto membro dell’area di lavoro e chiamiamolo piu_uno. Generiamo un nuovo crate libreria chiamato piu_uno:

$ cargo new piu_uno --lib
     Created library `piu_uno` package
      Adding `piu_uno` as member of workspace at `file:///progetti/somma`

Il file Cargo.toml nella cartella somma ora includerà il percorso piu_uno nell’elenco dei membri members:

File: Cargo.toml

[workspace]
resolver = "3"
members = ["sommatore", "piu_uno"]

La tua cartella somma dovrebbe ora contenere queste cartelle e questi file:

somma
├── Cargo.lock
├── Cargo.toml
├── piu_uno
│   ├── Cargo.toml
│   └── src
│       └── lib.rs
├── sommatore
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── target

Nel file piu_uno/src/lib.rs, aggiungiamo una funzione più_uno:

File: piu_uno/src/lib.rs

pub fn più_uno(x: i32) -> i32 {
    x + 1
}

Ora possiamo fare in modo che il pacchetto sommatore con il nostro binario dipenda dal pacchetto piu_uno che contiene la nostra libreria. Per prima cosa, dovremo aggiungere un percorso di dipendenza a piu_uno nel file sommatore/Cargo.toml.

File: sommatore/Cargo.toml

[dependencies]
piu_uno = { path = "../piu_uno" }

Cargo non presuppone che i crate dello stesso workspace dipendano l’uno dall’altro, quindi dobbiamo essere espliciti sulle relazioni di dipendenza.

Quindi, utilizziamo la funzione più_uno (dal crate piu_uno) nel crate sommatore. Apri il file sommatore/src/main.rs e modifica la funzione main per richiamare la funzione più_uno, come nel Listato 14-7

File: sommatore/src/main.rs
fn main() {
    let num = 10;
    println!("Ciao! {num} più uno fa {}!", piu_uno::più_uno(num));
}
Listato 14-7: Utilizzo del crate libreria piu_uno dal crate sommatore

Compiliamo lo spazio di lavoro eseguendo cargo build nella directory di primo livello somma!

$ cargo build
   Compiling piu_uno v0.1.0 (file:///progetti/somma/piu_uno)
   Compiling sommatore v0.1.0 (file:///progetti/somma/sommatore)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.22s

Per eseguire il crate binario dalla directory somma, possiamo specificare quale pacchetto del workspace vogliamo eseguire utilizzando l’argomento -p e il nome del pacchetto con cargo run:

$ cargo run -p sommatore
   Compiling sommatore v0.1.0 (file:///progetti/somma/sommatore)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/sommatore`
Ciao! 10 più uno fa 11!

Questo esegue il codice in sommatore/src/main.rs, che dipende dal crate piu_uno.

Dipendere da un Pacchetto Esterno

Avrai notato che lo spazio di lavoro ha un solo file Cargo.lock al livello superiore, invece di avere un Cargo.lock nella cartella di ogni crate. Questo assicura che tutti i crate utilizzino la stessa versione di tutte le dipendenze. Se aggiungiamo il pacchetto rand ai file sommatore/Cargo.toml e piu_uno/Cargo.toml, Cargo li risolverà entrambi in un’unica versione di rand e la registrerà nell’unico Cargo.lock. Fare in modo che tutti i crate nel workspace utilizzino le stesse dipendenze significa che i crate saranno sempre compatibili tra loro. Aggiungiamo il crate rand alla sezione [dependencies] nel file piu_uno/Cargo.toml in modo da poter utilizzare il crate rand nel crate piu_uno:

File: piu_uno/Cargo.toml

[dependencies]
rand = "0.8.5"

Ora possiamo aggiungere use rand; al file piu_uno/src/lib.rs e la creazione dell’intero workspace eseguendo cargo build nella cartella somma scaricherà e compilerà il crate rand. Riceveremo un avviso perché non stiamo effettivamente usando rand che abbiamo portato nello scope:

$ cargo build
    Updating crates.io index
  Downloaded rand v0.8.5
   --taglio--
   Compiling rand v0.8.5
   Compiling piu_uno v0.1.0 (file:///progetti/somma/piu_uno)
warning: unused import: `rand`
 --> piu_uno/src/lib.rs:1:5
  |
1 | use rand;
  |     ^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

warning: `piu_uno` (lib) generated 1 warning (run `cargo fix --lib -p piu_uno` to apply 1 suggestion)
   Compiling sommatore v0.1.0 (file:///progetti/somma/sommatore)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.95s

Il file Cargo.lock al livello più alto ora contiene informazioni sulla dipendenza di piu_uno da rand. Tuttavia, anche se rand è utilizzato da qualche parte nello spazio di lavoro, non possiamo utilizzarlo in altri crate del workspace a meno che non aggiungiamo rand anche ai loro file Cargo.toml. Ad esempio, se aggiungiamo use rand; al file sommatore/src/main.rs per il pacchetto sommatore, otterremo un errore:

$ cargo build
  --taglio--
   Compiling sommatore v0.1.0 (file:///progetti/somma/sommatore)
error[E0432]: unresolved import `rand`
 --> sommatore/src/main.rs:2:5
  |
2 | use rand;
  |     ^^^^ no external crate `rand`

Per risolvere questo problema, modifica il file Cargo.toml per il pacchetto sommatore e indica che rand è una dipendenza anche per esso. Costruendo il pacchetto sommatore aggiungerà rand all’elenco delle dipendenze di sommatore in Cargo.lock, ma non verranno scaricate copie aggiuntive di rand. Cargo farà in modo che ogni crate in ogni pacchetto dell’area di lavoro che utilizza il pacchetto rand utilizzi la stessa versione, a patto che specifichi versioni compatibili di rand, risparmiando spazio e assicurando che i crate nel workspace siano compatibili tra loro.

Se i crate nel workspace specificano versioni incompatibili della stessa dipendenza, Cargo risolverà ciascuna di esse, ma cercherà comunque di risolvere il minor numero possibile di versioni.

Aggiungere un Test a un Workspace

Per un altro miglioramento, aggiungiamo un test della funzione piu_uno::più_uno all’interno del crate piu_uno:

File: piu_uno/src/lib.rs

pub fn più_uno(x: i32) -> i32 {
    x + 1
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn funziona() {
        assert_eq!(3, più_uno(2));
    }
}

Ora esegui cargo test nella cartella di primo livello somma. Eseguendo cargo test in un workspace strutturato come questo, verranno eseguiti i test per tutti i crate presenti nello spazio di lavoro:

$ cargo test
   Compiling piu_uno v0.1.0 (file:///progetti/somma/piu_uno)
   Compiling sommatore v0.1.0 (file:///progetti/somma/sommatore)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.20s
     Running unittests src/lib.rs (target/debug/deps/piu_uno-93c49ee75dc46543)

running 1 test
test tests::funziona ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running unittests src/main.rs (target/debug/deps/sommatore-3a47283c568d2b6a)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests piu_uno

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

La prima sezione dell’output mostra che il test funziona nel crate piu_uno è passato. La sezione successiva mostra che sono stati trovati zero test nel crate sommatore e l’ultima sezione mostra che sono stati trovati zero test di documentazione nel crate piu_uno.

Possiamo anche eseguire i test per un particolare crate in un workspace dalla directory di primo livello utilizzando il flag -p e specificando il nome del crate che vogliamo testare:

$ cargo test -p piu_uno
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.00s
     Running unittests src/lib.rs (target/debug/deps/piu_uno-93c49ee75dc46543)

running 1 test
test tests::funziona ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests piu_uno

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Questo output mostra che cargo test ha eseguito solo i test del crate piu_uno e non ha eseguito i test del crate sommatore.

Se pubblichi i crate nello workspace su crates.io, ogni crate nello spazio di lavoro dovrà essere pubblicato separatamente. Come nel caso di cargo test, possiamo pubblicare un particolare crate nel nostro spazio di lavoro utilizzando il flag -p e specificando il nome del crate che vogliamo pubblicare.

Per fare ulteriore pratica, aggiungi un crate piu_due a questo spazio di lavoro in modo simile al crate piu_uno!

Quando il tuo progetto cresce, prendi in considerazione l’utilizzo di uno spazio di lavoro: ti permette di lavorare con componenti più piccoli e più facili da capire rispetto a un unico grande blocco di codice. Inoltre, mantenere i crate in uno spazio di lavoro può rendere più facile il coordinamento tra i crate se questi vengono spesso modificati nello stesso momento.