Sinora abbiamo sviluppato tutti i nostri programmi Rust all'interno del main
, la funzione che svolge il ruolo di entry point dell'esecuzione, il suo punto d'inizio, per così dire. Al di fuori del main
abbiamo sinora definito funzioni e struct
ma tutto era incluso all'interno di un unico file di estensione .rs
. Adesso però è giunto il momento di estendere i nostri orizzonti.
Da ora in poi avremo bisogno di sfruttare:
- moduli ovvero unità che innesteranno elementi sintattici definiti da noi o provenienti da altri contributor;
- librerie software con cui potremo organizzare il codice che scriviamo e condividerlo con altri progetti, spesso di altri autori;
- dipendenze ovvero l'indicazione di quali librerie software il nostro programma ha bisogno e come fare in modo che al suo avvio (tipicamente nel luogo di effettiva messa in produzione) si trovi a disposizione tutto l'ecosistema di librerie di cui ha bisogno.
Con questa lezione e quelle immediatamente successive, impareremo a gestire tutto questo e ciò ci permetterà di avere a disposizione gli strumenti per manovrare un vero flusso di lavoro da programmatore Rust.
I moduli in Rust
I moduli sono la componente fondamentale di tutta questa architettura e sono essenzialmente il modo che Rust offre per poter organizzare il codice suddividendolo in una gerarchia di unità, file e cartelle. Un altro aspetto fondamentale dei moduli consiste nella possibilità di gestire la visibilità delle componenti e ciò sarà basilare per definire cosa ha un ruolo "interno" al modulo e cosa è fatto per essere esportato.
Un esempio di modulo
Le parole chiave che ci accompagnano nell'utilizzo dei moduli sono essenzialmente tre:
mod
: crea un nuovo modulo e gli assegna il nome. E' seguito da un blocco di parentesi graffe che conterranno tutto ciò che il modulo comprende;pub
serve per definire quali elementi nel modulo sono pubblici ovvero visibili dall'esterno del modulo stesso;use
(che già conosciamo) specifica quali elementi di un modulo vogliamo usare nel programma.
Creiamo un modulo che contenga due funzioni, una chiamata saluta
che genera un messaggio di saluto ed un'altra, genera_saluto
, che crea la stringa di saluto in sé stessa: esempio dal bassissimo valore pratico ma estremamente importante a livello didattico!
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
}
}
Notiamo che una delle due funzioni, saluta
, è etichettata con pub
e ciò la rende visibile dall'esterno del modulo con il percorso modulo_saluto::saluta
dove modulo_saluto
è il nome del modulo.
Le regole dei moduli valgono anche nello stesso file. Pertanto anche scrivendo di seguito il main
dovremo indicare il percorso completo della funzione:
fn main() {
println!("{}", modulo_saluto::saluta());
}
Il risultato dell'output sarà, come si può immaginare, Messaggio: Ciao a tutti!.
Utilizzo di use in Rust
Come accennato prima, use
è la parola chiave che permette di utilizzare un elemento interno di un modulo solo con il suo nome e senza l'intero percorso.
Ad esempio, in questo caso possiamo usare:
use modulo_saluto::saluta;
Per permettere al main
di invocare saluta
solo con il suo nome. Così:
use modulo_saluto::saluta;
fn main() {
println!("{}", saluta());
}
Inoltre, use
può essere utilizzato con la parola chiave as
per indicare un nuovo nome che possiamo reputare più comodo e leggibile per l'elemento che stiamo importando:
use modulo_saluto::saluta as ciaociao;
fn main() {
println!("{}", ciaociao());
Dal momento in cui abbiamo definito ciaociao
, il nome saluta
non potrà più essere utilizzato direttamente ma esisterà ancora: unico particolare è che solo ciaociao
potrà essere invocato senza alcun percorso (anche perché trattandosi in effetti di un alias non ne dispone) mentre per chiamare direttamente la funzione dovremo appellarci al suo percorso completo modulo_saluto::saluta
.
Si faccia attenzione che use
non serve a modificare la visibilità ma solo a fare in modo di poter chiamare componenti di un modulo usando semplicemente il loro nome. In pratica, quello che propone è un'integrazione dell'attuale spazio dei nomi che estende il set di funzionalità invocabili a disposizione della porzione di programma che stiamo scrivendo.