Ora che sappiamo definire ed utilizzare i moduli in Rust, possiamo iniziare a pensare a come organizzare il nostro programma e sfruttare funzionalità messe a disposizione da altri programmatori, siano essi nostri colleghi o sviluppatori collocati in chissà quali altri posti del mondo.
Un termine di cui sentiremo molto parlare in Rust è crate ed è il concetto che normalmente incarna quello più generico di libreria software. Parliamo quindi di funzionalità "impacchettate" che vengono compilate ed incluse in un'unica unità per fare in modo di essere utilizzate da programmi sviluppati dagli stessi sviluppatori della libreria o, come molto spesso capita, anche da altri.
In questa lezione il nostro scopo sarà quello di comprendere come creare e compilare una libreria software in Rust ed iniziare a conoscere il mondo dei crate. Quello che alla fine di questa lezione ci mancherà ancora (ma lo faremo nella prossima) sarà uno strumento software importantissimo, cargo, che è il package manager di Rust, il tool con cui potremo mettere insieme tutti i moduli e le dipendenze di cui avremo bisogno per poter attivare il nostro programma.
Creare una libreria software
Trasformiamo l'esempio della lezione precedente in una libreria software ma non definendolo come modulo e usando solo le funzioni che all'interno, comprensive di modificatori di visibilità. In questo modo:
fn genera_saluto() -> &'static str {
"Ciao a tutti!"
}
pub fn saluta() -> String {
let mut saluto: String = "Messaggio: ".to_owned();
saluto.push_str(genera_saluto());
saluto
}
Inseriamo il tutto in un file autonomo, di nome saluto.rs
che non contiene quindi il main
. Quello che stiamo facendo è, in pratica, isolare queste funzionalità in modo che possano guadagnarsi una propria autonomia per essere impiegate nei progetti che le richiedano e che ne abbiano bisogno.
Non abbiamo usato il costrutto del modulo (cosa assolutamente non vietata) al solo scopo di dimostrare come, una volta creato il crate, Rust ci permetta di averlo a disposizione proprio nella modalità in cui abbiamo imparato a sfruttare i moduli.
Procediamo a compilare la nostra libreria in formato crate utilizzando, da riga di comando, il tool per la compilazione rustc
:
$ rustc --crate-type=lib saluto.rs
Fatto ciò troveremo nel nostro spazio di lavoro un nuovo file libsaluto.rlib
. Non siamo stati noi a ribattezzarlo così ma ci ha pensato Rust in quanto il nome del crate deve iniziare con lib
e deve avere estensione .rlib
.
Arriva ora il momento di usarlo in un nostro programma.
Chi userà il crate
Scriviamo ora il programma che sfrutterà il crate e sarà composto semplicemente da un main
.
fn main() {
println!("{}", modulo_saluto::saluta());
}
Come vediamo, nominiamo un modulo modulo_saluto
che in realtà non abbiamo mai creato. Questo perché al momento della compilazione di questo programma dovremo prendere due accorgimenti. Il primo consisterà nell'informare il compilatore che le funzionalità di cui ha bisogno si trovano all'interno di un crate esterno, il secondo sarà che assegneremo al crate il nome modulo_saluto
per indicarlo all'interno del programma.
Ecco il comando per la compilazione:
$ rustc main.rs --extern modulo_saluto=libsaluto.rlib
Osserviamo che in questa istruzione abbiamo chiesto di compilare main.rs
(il programma di cui abbiamo appena riportato il codice) e abbiamo avvisato che esiste una componente esterna, già compilata, di cui dobbiamo tenere conto e che sarà rinominata all'interno del codice come modulo_saluto
.
Fatto ciò avremo nel nostro spazio di lavoro un eseguibile di nome main
che potremo invocare:
$ ./main
Messaggio: Ciao a tutti!
Il tutto ha funzionato perfettamente.
Usare moduli nei crate di Rust
Sarà tuttavia molto comune usare moduli nei crate e, spesso, se ne creeranno più di uno nello stesso crate per organizzare le funzionalità. Vediamo il medesimo esempio con un modulo tenendo in considerazione che il modulo stesso dovrà essere etichettato con pub
per poter essere usato dall'esterno.
Il file saluto.rs
diventa così:
pub mod modulo_saluto{
fn genera_saluto() -> &'static str {
"Ciao a tutti!"
}
pub fn saluta() -> String {
let mut saluto: String = "Messaggio: ".to_owned();
saluto.push_str(genera_saluto());
saluto
}
}
La sua compilazione rimarrà la stessa di prima:
$ rustc --crate-type=lib saluto.rs
A questo punto, in fase di compilazione del main.rs
dovremo dare un nome alla dipendenza che importeremo (in questo caso useremo libreria_saluto
) ed all'interno, prima di usare use
e tutte le altre comodità dei moduli, dovremo usare extern crate
per avvisare che stiamo impiegando qualcosa presente in un'altra libreria:
extern crate libreria_saluto;
use libreria_saluto::modulo_saluto::saluta;
fn main() {
println!("{}", saluta());
}
Lo compileremo così:
$ rustc main.rs --extern libreria_saluto=libsaluto.rlib
ed otterremo un'esecuzione identica alla precedente con la corretta pubblicazione del messaggio.