Percorsi per Fare Riferimento a un Elemento nell’Albero dei Moduli
Per mostrare a Rust dove trovare un elemento in un albero dei moduli, utilizziamo un path (percorso) nello stesso modo in cui usiamo un path quando navighiamo in un filesystem. Per chiamare una funzione, dobbiamo conoscere il suo path.
Un path può assumere due forme:
- Un path assoluto è il percorso completo che inizia dalla radice del crate;
per il codice di un crate esterno, il percorso assoluto inizia con il nome
del crate, e per il codice del crate corrente, inizia con il letterale
crate
. - Un path relativo inizia dal modulo corrente e utilizza
self
,super
o un identificatore nel modulo corrente.
Sia i path assoluti che relativi sono seguiti da uno o più identificatori
separati da doppi due punti (::
).
Tornando al Listato 7-1, supponiamo di voler chiamare la funzione
aggiungi_in_lista
. Questo è lo stesso che chiedere: qual è il path della
funzione aggiungi_in_lista
? Il Listato 7-3 contiene il Listato 7-1 con alcuni
dei moduli e delle funzioni rimossi.
Mostreremo due modi per chiamare la funzione aggiungi_in_lista
da una nuova
funzione, mangiare_al_ristorante
, definita nella radice del crate. Questi
path sono corretti, ma c’è un altro problema che impedirà a questo esempio di
compilare così com’è. Spiegheremo perché tra poco.
La funzione mangiare_al_ristorante
fa parte dell’API pubblica del nostro
crate libreria, quindi la contrassegniamo con la parola chiave pub
. Nella
sezione “Esporre Path con la Parola Chiave pub
”,
entreremo nei dettagli di pub
.
mod sala {
mod accoglienza {
fn aggiungi_in_lista() {}
}
}
pub fn mangiare_al_ristorante() {
// Path assoluta
crate::sala::accoglienza::aggiungi_in_lista();
// Path relativa
sala::accoglienza::aggiungi_in_lista();
}
aggiungi_in_lista
utilizzando path assoluti e relativiLa prima volta che chiamiamo la funzione aggiungi_in_lista
in
mangiare_al_ristorante
, utilizziamo un path assoluto. La funzione
aggiungi_in_lista
è definita nello stesso crate di mangiare_al_ristorante
,
il che significa che possiamo usare la parola chiave crate
per iniziare un
path assoluto. Includiamo quindi ciascuno dei moduli successivi fino a
raggiungere aggiungi_in_lista
. Puoi immaginare un filesystem con la stessa
struttura: specificheremmo il path /sala/accoglienza/aggiungi_in_lista
per
eseguire il programma aggiungi_in_lista
; utilizzare il nome del crate per
partire dalla radice del crate è come usare /
per partire dalla radice del
filesystem nel tuo terminale.
La seconda volta che chiamiamo aggiungi_in_lista
in mangiare_al_ristorante
,
utilizziamo un path relativo. Il path inizia con sala
, il nome del modulo
definito allo stesso livello dell’albero dei moduli di mangiare_al_ristorante
.
Qui l’equivalente nel filesystem sarebbe utilizzare il path
sala/accoglienza/aggiungi_in_lista
. Iniziare con un nome di modulo significa
che il path è relativo.
Scegliere se utilizzare un path relativo o assoluto è una decisione che
prenderai in base al tuo progetto, e dipende da se è più probabile che tu sposti
il codice di definizione dell’elemento separatamente o insieme al codice che
utilizza l’elemento. Ad esempio, se spostassimo il modulo sala
e la funzione
mangiare_al_ristorante
in un modulo chiamato gestione_cliente
, dovremmo
aggiornare il path assoluto per aggiungi_in_lista
, ma il path relativo
rimarrebbe valido. Tuttavia, se spostassimo la funzione mangiare_al_ristorante
separatamente in un modulo chiamato cena
, il path assoluto per la chiamata a
aggiungi_in_lista
rimarrebbe lo stesso, ma il path relativo dovrebbe essere
aggiornato. La nostra preferenza in generale è specificare path assoluti
perché è più probabile che vogliamo spostare le definizioni di codice e le
chiamate agli elementi in modo indipendente l’una dall’altra.
Proviamo a compilare il Listato 7-3 e scopriamo perché non si compila ancora! Gli errori che otteniamo sono mostrati nel Listato 7-4.
$ cargo build
Compiling ristorante v0.1.0 (file:///progetti/ristorante)
error[E0603]: module `accoglienza` is private
--> src/lib.rs:9:18
|
9 | crate::sala::accoglienza::aggiungi_in_lista();
| ^^^^^^^^^^^ ----------------- function `aggiungi_in_lista` is not publicly re-exported
| |
| private module
|
note: the module `accoglienza` is defined here
--> src/lib.rs:2:5
|
2 | mod accoglienza {
| ^^^^^^^^^^^^^^^
error[E0603]: module `accoglienza` is private
--> src/lib.rs:12:11
|
12 | sala::accoglienza::aggiungi_in_lista();
| ^^^^^^^^^^^ ----------------- function `aggiungi_in_lista` is not publicly re-exported
| |
| private module
|
note: the module `accoglienza` is defined here
--> src/lib.rs:2:5
|
2 | mod accoglienza {
| ^^^^^^^^^^^^^^^
For more information about this error, try `rustc --explain E0603`.
error: could not compile `ristorante` (lib) due to 2 previous errors
I messaggi di errore dicono che il modulo accoglienza
è privato. In altre
parole, abbiamo i path corretti per il modulo accoglienza
e la funzione
aggiungi_in_lista
, ma Rust non ci permette di usarli perché non ha accesso
alle sezioni private. In Rust, tutti gli elementi (funzioni, metodi, struct,
enum, moduli e costanti) sono privati rispetto ai moduli genitore come
impostazione predefinita. Se desideri rendere un elemento come una funzione o
una struct privata, lo metti in un modulo.
Gli elementi in un modulo genitore non possono utilizzare gli elementi privati all’interno dei moduli figli, ma gli elementi nei moduli figli possono utilizzare gli elementi nei loro moduli antenati. Questo perché i moduli figli incapsulano e nascondono i loro dettagli di implementazione, ma i moduli figli possono vedere il contesto in cui sono definiti. Per continuare con la nostra metafora, pensa alle regole di privacy come se fossero l’ufficio posteriore di un ristorante: ciò che accade lì è privato e nascosto per i clienti del ristorante, ma i manager dell’ufficio possono vedere e fare tutto nel ristorante che gestiscono.
Rust ha scelto che far funzionare il sistema dei moduli in questo modo,
nascondendo i dettagli di implementazione interni come impostazione predefinita.
In questo modo, sai quali parti del codice interno puoi modificare senza
compromettere il codice esterno. Tuttavia, Rust ti offre la possibilità di
esporre le parti interne del codice dei moduli figli ai moduli antenati
utilizzando la parola chiave pub
per rendere pubblico un elemento.
Esporre Path con la Parola Chiave pub
Torniamo all’errore nel Listato 7-4 che ci diceva che il modulo accoglienza
è
privato. Vogliamo che la funzione mangiare_al_ristorante
nel modulo genitore
abbia accesso alla funzione aggiungi_in_lista
nel modulo figlio, quindi
contrassegniamo il modulo accoglienza
con la parola chiave pub
, come
mostrato nel Listato 7-5.
mod sala {
pub mod accoglienza {
fn aggiungi_in_lista() {}
}
}
// --taglio--
pub fn mangiare_al_ristorante() {
// Path assoluta
crate::sala::accoglienza::aggiungi_in_lista();
// Path relativa
sala::accoglienza::aggiungi_in_lista();
}
accoglienza
come pub
per usarlo da mangiare_al_ristorante
Sfortunatamente, il codice nel Listato 7-5 genera ancora errori del compilatore, come mostrato nel Listato 7-6.
$ cargo build
Compiling ristorante v0.1.0 (file:///progetti/ristorante)
error[E0603]: function `aggiungi_in_lista` is private
--> src/lib.rs:12:31
|
12 | crate::sala::accoglienza::aggiungi_in_lista();
| ^^^^^^^^^^^^^^^^^ private function
|
note: the function `aggiungi_in_lista` is defined here
--> src/lib.rs:4:9
|
4 | fn aggiungi_in_lista() {}
| ^^^^^^^^^^^^^^^^^^^^^^
error[E0603]: function `aggiungi_in_lista` is private
--> src/lib.rs:15:24
|
15 | sala::accoglienza::aggiungi_in_lista();
| ^^^^^^^^^^^^^^^^^ private function
|
note: the function `aggiungi_in_lista` is defined here
--> src/lib.rs:4:9
|
4 | fn aggiungi_in_lista() {}
| ^^^^^^^^^^^^^^^^^^^^^^
For more information about this error, try `rustc --explain E0603`.
error: could not compile `ristorante` (lib) due to 2 previous errors
Cosa è successo? Aggiungere la parola chiave pub
davanti a mod accoglienza
rende il modulo pubblico. Con questa modifica, se possiamo accedere a sala
,
possiamo accedere a accoglienza
. Ma i contenuti di accoglienza
sono ancora
privati; rendere pubblico il modulo non rende pubblici i suoi contenuti. La
parola chiave pub
su un modulo consente solo al codice nei suoi moduli
genitore di fare riferimento ad esso, non di accedere al suo codice interno.
Poiché i moduli sono contenitori, non possiamo fare molto semplicemente rendendo
pubblico il modulo; dobbiamo andare oltre e scegliere di rendere pubblici uno o
più degli elementi all’interno del modulo.
Gli errori nel Listato 7-6 dicono che la funzione aggiungi_in_lista
è privata.
Le regole di privacy si applicano a struct, enum, funzioni e metodi, così
come ai moduli.
Facciamo in modo che anche la funzione aggiungi_in_lista
sia pubblica
aggiungendo la parola chiave pub
prima della sua definizione, come nel Listato
7-7.
mod sala {
pub mod accoglienza {
pub fn aggiungi_in_lista() {}
}
}
// --taglio--
pub fn mangiare_al_ristorante() {
// Path assoluta
crate::sala::accoglienza::aggiungi_in_lista();
// Path relativa
sala::accoglienza::aggiungi_in_lista();
}
pub
a mod accoglienza
e fn aggiungi_in_lista
ci consente di chiamare la funzione da mangiare_al_ristorante
Ora il codice si compilerà! Per capire perché aggiungere la parola chiave pub
ci consente di utilizzare questi path in mangiare_al_ristorante
rispetto
alle regole di privacy, diamo un’occhiata ai path assoluti e relativi.
Nel path assoluto, iniziamo con crate
, la radice dell’albero dei moduli del
nostro crate. Il modulo sala
è definito nella radice del crate. Anche se
sala
non è pubblico, poiché la funzione mangiare_al_ristorante
è definita
nello stesso modulo di sala
(cioè, mangiare_al_ristorante
e sala
sono
fratelli), possiamo fare riferimento a sala
da mangiare_al_ristorante
.
Successivamente, c’è il modulo accoglienza
contrassegnato con pub
. Possiamo
accedere al modulo genitore di accoglienza
, quindi possiamo accedere a
accoglienza
. Infine, la funzione aggiungi_in_lista
è contrassegnata con
pub
e possiamo accedere al suo modulo genitore, quindi questa chiamata di
funzione funziona!
Nel path relativo, la logica è la stessa del path assoluto, tranne per il
primo passaggio: invece di partire dalla radice del crate, il path inizia da
sala
. Il modulo sala
è definito all’interno dello stesso modulo di
mangiare_al_ristorante
, quindi il path relativo che inizia dal modulo in cui
è definita mangiare_al_ristorante
funziona. Poi, poiché accoglienza
e
aggiungi_in_lista
sono contrassegnati con pub
, il resto del path funziona,
e questa chiamata di funzione è valida!
Se prevedi di condividere il tuo crate libreria affinché altri progetti possano utilizzare il tuo codice, la tua API pubblica è il tuo contratto con gli utenti del tuo crate che determina come possono interagire con il tuo codice. Ci sono molte considerazioni relative alla gestione delle modifiche alla tua API pubblica per facilitare la dipendenza delle persone dal tuo crate. Queste considerazioni vanno oltre l’ambito di questo libro; se sei interessato a questo argomento, consulta Le Linee Guida per l’API di Rust.
Buone Pratiche per Pacchetti con un Binario e una Libreria
Abbiamo menzionato che un pacchetto può contenere sia una radice di crate binario src/main.rs che una radice di crate libreria src/lib.rs, e entrambi i crate avranno il nome del pacchetto come impostazione predefinita. Tipicamente, i pacchetti che contengono sia una libreria che un crate binario avranno nel crate binario il codice strettamente necessario ad avviare un eseguibile che chiama il codice definito nel crate libreria. Questo consente ad altri progetti di beneficiare della maggior parte delle funzionalità che il pacchetto fornisce, poiché il codice del crate libreria può essere condiviso.
L’albero dei moduli dovrebbe essere definito in src/lib.rs. Quindi, qualsiasi elemento pubblico può essere utilizzato nel crate binario facendo iniziare i path con il nome del pacchetto. Il crate binario diventa un utilizzatore del crate libreria proprio come un crate completamente esterno utilizzerebbe il crate libreria: può utilizzare solo l’API pubblica. Questo ti aiuta a progettare una buona API; non solo sei l’autore, ma sei anche un cliente!
Nel Capitolo 12, dimostreremo questa pratica organizzativa con un programma da riga di comando che conterrà sia un crate binario che un crate libreria.
Iniziare Path Relative con super
Possiamo costruire path relative che iniziano nel modulo genitore, piuttosto
che nel modulo corrente o nella radice del crate, utilizzando super
all’inizio del path. Questo è simile a iniziare un path del filesystem con
la sintassi ..
che significa andare nella directory genitore. Utilizzare
super
ci consente di fare riferimento a un elemento che sappiamo essere nel
modulo genitore, il che può rendere più facile riorganizzare l’albero dei moduli
quando il modulo è strettamente correlato al genitore, ma il genitore potrebbe
essere spostato altrove nell’albero dei moduli in futuro.
Considera il codice nel Listato 7-8 che modella la situazione in cui un cuoco
corregge un ordine errato e lo porta personalmente al cliente. La funzione
correzione_ordine
definita nel modulo cucine
chiama la funzione
servi_ordine
definita nel modulo genitore specificando il path per
servi_ordine
, iniziando con super
.
fn servi_ordine() {}
mod cucine {
fn correzione_ordine() {
cucina_ordine();
super::servi_ordine();
}
fn cucina_ordine() {}
}
super
La funzione correzione_ordine
si trova nel modulo cucine
, quindi possiamo
usare super
per andare al modulo genitore di cucine
, che in questo caso è
crate
, la radice. Da lì, cerchiamo servi_ordine
e lo troviamo. Successo!
Pensiamo che il modulo cucine
e la funzione servi_ordine
siano probabilmente
destinati a rimanere nella stessa relazione l’uno con l’altro e verranno
spostati insieme se decidiamo di riorganizzare l’albero dei moduli del crate.
Pertanto abbiamo usato super
in modo da avere meno posti da aggiornare nel
codice in futuro se questo codice viene spostato in un modulo diverso.
Rendere Pubbliche Struct e Enum
Possiamo anche utilizzare pub
per designare struct ed enum come pubblici,
ma ci sono alcuni dettagli aggiuntivi nell’uso di pub
con struct ed enum.
Se utilizziamo pub
prima di una definizione di struct, rendiamo la struct
pubblica, ma i campi della struct rimarranno privati (come per i moduli).
Possiamo rendere pubblici o meno ciascun campo caso per caso. Nel Listato 7-9,
abbiamo definito una struct pubblica cucine::Colazione
con un campo pubblico
toast
ma un campo privato frutta_di_stagione
. Questo modella il caso in un
ristorante in cui il cliente può scegliere il tipo di pane che accompagna un
pasto, ma il cuoco decide quale frutta accompagna il pasto in base a ciò che è
di stagione e disponibile. La frutta disponibile cambia rapidamente, quindi i
clienti non possono scegliere la frutta o vedere quale frutta riceveranno.
mod cucine {
pub struct Colazione {
pub toast: String,
frutta_di_stagione: String,
}
impl Colazione {
pub fn estate(toast: &str) -> Colazione {
Colazione {
toast: String::from(toast),
frutta_di_stagione: String::from("pesche"),
}
}
}
}
pub fn mangiare_al_ristorante() {
// Ordina una colazione in estate con pane tostato di segale.
let mut pasto = cucine::Colazione::estate("segale");
// Cambiare idea sul pane che vorremmo.
pasto.toast = String::from("integrale");
println!("Vorrei un toast {}, grazie.", pasto.toast);
// La riga successiva non verrà compilata se la de-commentiamo; non
// ci è permesso vedere o modificare frutta che accompagna il pasto.
// pasto.frutta_di_stagione = String::from("mirtilli");
}
Poiché il campo toast
nella struct cucine::Colazione
è pubblico, in
mangiare_al_ristorante
possiamo scrivere e leggere il campo toast
utilizzando la notazione a punto. Nota che non possiamo utilizzare il campo
frutta_di_stagione
in mangiare_al_ristorante
, perché frutta_di_stagione
è
privato. Prova a de-commentare la riga che modifica il valore del campo
frutta_di_stagione
per vedere quale errore ottieni!
Inoltre, nota che poiché cucine::Colazione
ha un campo privato, la struct
deve fornire una funzione associata pubblica che costruisce un’istanza di
Colazione
(qui l’abbiamo chiamata estate
). Se Colazione
non avesse una
funzione del genere, non potremmo creare un’istanza di Colazione
in
mangiare_al_ristorante
perché non potremmo impostare il valore del campo
privato frutta_di_stagione
in mangiare_al_ristorante
.
Al contrario, se rendiamo un’enum pubblico, tutte le sue varianti diventano
pubbliche. Abbiamo bisogno solo di pub
prima della parola chiave enum
, come
mostrato nel Listato 7-10.
mod cucine {
pub enum Antipasti {
Zuppa,
Insalata,
}
}
pub fn mangiare_al_ristorante() {
let ordine1 = cucine::Antipasti::Zuppa;
let ordine2 = cucine::Antipasti::Insalata;
}
Poiché abbiamo reso pubblico l’enum Antipasti
, possiamo utilizzare le
varianti Zuppa
e Insalata
in mangiare_al_ristorante
.
Le enum non sono molto utili a meno che le loro varianti non siano pubbliche;
sarebbe fastidioso dover annotare tutte le varianti delle enum con pub
una
ad una, quindi la norma per le varianti delle enum è essere pubbliche. Le
struct sono spesso utili senza che i loro campi siano pubblici, quindi i campi
delle struct seguono la regola generale che tutto è privato come impostazione
predefinita, a meno che non sia annotato con pub
.
C’è un’ultima situazione che coinvolge pub
che non abbiamo trattato, ed è la
nostra ultima caratteristica del sistema dei moduli: la parola chiave use
.
Tratteremo use
da solo prima, e poi mostreremo come combinare pub
e use
.