SE VUOI PRENDERE LA CERTIFICAZIONE PER QUESTO CORSO CLICCA QUI
Portare Percorsi nello Scope con la Parola Chiave use
Dover scrivere i percorsi per chiamare le funzioni può sembrare scomodo e ripetitivo. Nell’Elenco 7-7, che scegliessimo il percorso assoluto o relativo per la funzione add_to_waitlist, ogni volta che volevamo chiamare add_to_waitlist dovevamo specificare anche front_of_house e hosting. Fortunatamente, c’è un modo per semplificare questo processo: possiamo creare un shortcut per un percorso con la parola chiave use una volta, e poi usare il nome più breve ovunque altro nello scope.
Nell’Elenco 7-11, portiamo il modulo crate::front_of_house::hosting nello scope della funzione eat_at_restaurant in modo che dobbiamo specificare solo hosting::add_to_waitlist per chiamare la funzione add_to_waitlist in eat_at_restaurant.
rust
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
} use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
Aggiungere use e un percorso in uno scope è simile a creare un link simbolico nel filesystem. Aggiungendo use crate::front_of_house::hosting nel crate root, hosting è ora un nome valido in quello scope, proprio come se il modulo hosting fosse stato definito nel crate root. I percorsi portati nello scope con use controllano anche la privacy, come qualsiasi altro percorso.
Nota che use crea solo lo shortcut per lo specifico scope in cui avviene l’uso. L’Elenco 7-12 sposta la funzione eat_at_restaurant in un nuovo modulo figlio chiamato customer, che è quindi uno scope diverso dalla dichiarazione use, quindi il corpo della funzione non verrà compilato:
rust
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
} use crate::front_of_house::hosting;
mod customer {
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
}
L’errore del compilatore mostra che lo shortcut non si applica più all’interno del modulo customer:
less
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
warning: unused import: `crate::front_of_house::hosting`
--> src/lib.rs:7:5
|
7 | use crate::front_of_house::hosting;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default error[E0433]: failed to resolve: use of undeclared crate or module `hosting`
--> src/lib.rs:11:9
|
11 | hosting::add_to_waitlist();
| ^^^^^^^ use of undeclared crate or module `hosting`
For more information about this error, try `rustc --explain E0433`.
warning: `restaurant` (lib) generated 1 warning
error: could not compile `restaurant` due to previous error; 1 warning emitted
Nota che c’è anche un avviso che l’uso non viene più utilizzato nel suo scope! Per risolvere questo problema, sposta l’uso anche all’interno del modulo customer, o fa riferimento allo shortcut nel modulo genitore con super::hosting all’interno del modulo figlio customer.
Creazione di Percorsi use Idiomatici
Nell’Elenco 7-11, potresti aver pensato perché abbiamo specificato use crate::front_of_house::hosting e poi chiamato hosting::add_to_waitlist in eat_at_restaurant anziché specificare il percorso use fino alla funzione add_to_waitlist per ottenere lo stesso risultato, come nell’Elenco 7-13.
rust
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
} use crate::front_of_house::hosting::add_to_waitlist;
pub fn eat_at_restaurant() {
add_to_waitlist();
}
Anche se sia l’Elenco 7-11 che il 7-13 completano lo stesso compito, l’Elenco 7-11 è il modo idiomatico di portare una funzione nello scope con use. Portare il modulo genitore della funzione nello scope con use significa che dobbiamo specificare il modulo genitore quando chiamiamo la funzione. Specificare il modulo genitore durante la chiamata della funzione chiarisce che la funzione non è definita localmente pur minimizzando la ripetizione del percorso completo. Il codice nell’Elenco 7-13 non è chiaro su dove sia definita add_to_waitlist.
D’altra parte, quando si portano in scope strutture, enumerazioni e altri elementi con use, è idiomatico specificare il percorso completo. L’Elenco 7-14 mostra il modo idiomatico di portare la struttura HashMap della libreria standard nello scope di un crate binario.
rust
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, 2);
}
Non c’è una forte ragione dietro a questo idiom: è solo la convenzione che è emersa, e le persone si sono abituate a leggere e scrivere codice Rust in questo modo.
L’eccezione a questo idiom è se stiamo portando due elementi con lo stesso nome nello scope con dichiarazioni use, perché Rust non lo permette. L’Elenco 7-15 mostra come portare in scope due tipi Result che hanno lo stesso nome ma moduli genitori diversi e come fare riferimento a essi.
rust
use std::fmt;
use std::io; fn function1() -> fmt::Result {
// --snip--
}
fn function2() -> io::Result<()> {
// --snip--
}
Come puoi vedere, utilizzare i moduli genitori distingue i due tipi Result. Se invece avessimo specificato use std::fmt::Result e use std::io::Result, avremmo avuto due tipi Result nello stesso scope e Rust non avrebbe saputo a quale ci riferivamo quando abbiamo usato Result.
Fornire Nuovi Nomi con la Parola Chiave as
C’è un’altra soluzione al problema di portare due tipi dello stesso nome nello stesso scope con use: dopo il percorso, possiamo specificare as
e un nuovo nome locale, o alias, per il tipo. L’Elenco 7-16 mostra un altro modo per scrivere il codice nell’Elenco 7-15 rinominando uno dei due tipi Result
usando as
.
rust
use std::fmt::Result;
use std::io::Result as IoResult; fn function1() -> Result {
// --snip--
}
fn function2() -> IoResult<()> {
// --snip--
}
Nella seconda dichiarazione use, abbiamo scelto il nuovo nome IoResult
per il tipo std::io::Result
, che non entrerà in conflitto con Result
di std::fmt
che abbiamo anch’esso portato in scope. L’Elenco 7-15 e l’Elenco 7-16 sono considerati idiomatici, quindi la scelta è tua!
Ri-esportare Nomi con pub use
Quando portiamo un nome in scope con la parola chiave use
, il nome disponibile nel nuovo scope è privato. Per consentire al codice che chiama il nostro codice di fare riferimento a quel nome come se fosse stato definito nello scope di quel codice, possiamo combinare pub
e use
. Questa tecnica è chiamata ri-esportazione perché stiamo portando un elemento in scope ma stiamo anche rendendo quell’elemento disponibile per gli altri che vogliono portarlo nel loro scope.
L’Elenco 7-17 mostra il codice dell’Elenco 7-11 con use
nel modulo radice cambiato in pub use
.
rust
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
} pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
Prima di questa modifica, il codice esterno avrebbe dovuto chiamare la funzione add_to_waitlist
utilizzando il percorso restaurant::front_of_house::hosting::add_to_waitlist()
. Ora che questo pub use
ha ri-esportato il modulo hosting
dal modulo radice, il codice esterno può ora utilizzare il percorso restaurant::hosting::add_to_waitlist()
invece.
La ri-esportazione è utile quando la struttura interna del tuo codice è diversa da come i programmatori che chiamano il tuo codice penserebbero al dominio. Ad esempio, in questa metafora del ristorante, le persone che gestiscono il ristorante pensano a “front of house” e “back of house”. Ma i clienti che visitano un ristorante probabilmente non penseranno alle parti del ristorante in quei termini. Con pub use
, possiamo scrivere il nostro codice con una struttura ma esporre una struttura diversa. Farlo rende la nostra libreria ben organizzata per i programmatori che lavorano sulla libreria e i programmatori che chiamano la libreria. Guarderemo un altro esempio di pub use
e come influisce sulla documentazione della tua crate nella sezione “Esportare una API Pubblica Conveniente con pub use
” del Capitolo 14.
Utilizzo di Pacchetti Esterni
Nel Capitolo 2, abbiamo programmato un progetto di un gioco di indovinelli che utilizzava un pacchetto esterno chiamato rand
per ottenere numeri casuali. Per utilizzare rand
nel nostro progetto, abbiamo aggiunto questa riga a Cargo.toml:
toml
rand = "0.8.5"
Aggiungere rand
come dipendenza in Cargo.toml dice a Cargo di scaricare il pacchetto rand
e tutte le dipendenze da crates.io e rendere rand
disponibile per il nostro progetto.
Quindi, per portare le definizioni di rand
nello scope del nostro pacchetto, abbiamo aggiunto una riga use
che inizia con il nome della crate, rand
, e abbiamo elencato gli elementi che volevamo portare in scope. Ricorda che nella sezione “Generare un Numero Casuale” nel Capitolo 2, abbiamo portato il trait Rng
nello scope e chiamato la funzione rand::thread_rng()
.
rust
use rand::Rng;
fn main() {
let secret_number = rand::thread_rng().gen_range(1..=100);
}
I membri della comunità Rust hanno reso disponibili molti pacchetti su crates.io e portarne uno qualsiasi nel tuo pacchetto coinvolge questi stessi passaggi: elencarli nel file Cargo.toml del tuo pacchetto e utilizzare use
per portare gli elementi delle loro crates in scope.
Nota che la libreria standard std è anche una crate esterna al nostro pacchetto. Poiché la libreria standard è inclusa con il linguaggio Rust, non dobbiamo modificare Cargo.toml per includere std. Ma dobbiamo fare riferimento ad essa con use
per portare gli elementi da lì nello scope del nostro pacchetto. Ad esempio, con HashMap
useremmo questa riga:
rust
use std::collections::HashMap;
Questo è un percorso assoluto che inizia con std
, il nome della crate della libreria standard.
Utilizzo di Percorsi Anidati per Pulire le Lunghe Liste di use
Se stiamo utilizzando più elementi definiti nella stessa crate o nello stesso modulo, elencare ciascun elemento su una propria riga può occupare molto spazio verticale nei nostri file. Ad esempio, queste due dichiarazioni use
che avevamo nel Gioco di Indovinelli nell’Elenco 2-4 portano gli elementi di std
nello scope:
rust
// --snip--
use std::cmp::Ordering;
use std::io;
// --snip--
Invece, possiamo utilizzare i percorsi annidati per portare gli stessi elementi nello scope in una sola riga. Facciamo ciò specificando la parte comune del percorso, seguita da due due punti, e quindi parentesi graffe attorno a un elenco delle parti dei percorsi che differiscono, come mostrato nell’Elenco 7-18.
rust
// --snip--
use std::{cmp::Ordering, io};
// --snip--
In programmi più grandi, portare molti elementi in scope dalla stessa crate o dallo stesso modulo utilizzando i percorsi annidati può ridurre di molto il numero di dichiarazioni use
separate necessarie!
Possiamo utilizzare un percorso annidato a qualsiasi livello in un percorso, il che è utile quando si combinano due dichiarazioni use
che condividono un sotto-percorso. Ad esempio, l’Elenco 7-19 mostra due dichiarazioni use
: una che porta std::io
nello scope e una che porta std::io::Write
nello scope.
rust
// --snip--
use std::io;
use std::io::Write;
La parte comune di questi due percorsi è std::io
, e questo è il primo percorso completo. Per unire questi due percorsi in una sola dichiarazione use
, possiamo usare self
nel percorso annidato, come mostrato nell’Elenco 7-20.
rust
// --snip--
use std::io::{self, Write};
Questa riga porta std::io
e std::io::Write
nello scope.