Portare i Percorsi in Scope con la Parola Chiave use
Dover scrivere i percorsi (path) per chiamare le funzioni può risultare
scomodo e ripetitivo. Nel Listato 7-7, sia che scegliessimo il path assoluto o
relativo per la funzione aggiungi_in_lista
, ogni volta che volevamo chiamare
aggiungi_in_lista
dovevamo specificare anche sala
e accoglienza
.
Fortunatamente esiste un modo per semplificare questo processo: possiamo creare
un collegamento rapido a un path con la parola chiave use
una volta, e poi
usare il nome più corto nel resto dello scope.
Nel Listato 7-11, portiamo il modulo crate::sala::accoglienza
nello scope
della funzione mangiare_al_ristorante
così da dover specificare solo
accoglienza::aggiungi_in_lista
per chiamare la funzione aggiungi_in_lista
in
mangiare_al_ristorante
.
mod sala {
pub mod accoglienza {
pub fn aggiungi_in_lista() {}
}
}
use crate::sala::accoglienza;
pub fn mangiare_al_ristorante() {
accoglienza::aggiungi_in_lista();
}
use
Aggiungere use
e un path in uno scope è simile a creare un collegamento
simbolico nel filesystem. Aggiungendo use crate::sala::accoglienza
nella
radice (root) del crate, accoglienza
è ora un nome valido in quello
scope, come se il modulo accoglienza
fosse stato definito nella radice del
crate. I path portati nello scope con use
rispettano anche la privacy,
come qualsiasi altro path.
Nota che use
crea il collegamento rapido solo per lo scope particolare in
cui use
è dichiarato. Il Listato 7-12 sposta la funzione
mangiare_al_ristorante
in un nuovo modulo figlio chiamato cliente
, che ora è
in uno scope diverso rispetto alla dichiarazione use
, quindi il corpo della
funzione non si compilerà.
mod sala {
pub mod accoglienza {
pub fn aggiungi_in_lista() {}
}
}
use crate::sala::accoglienza;
mod cliente {
pub fn mangiare_al_ristorante() {
accoglienza::aggiungi_in_lista();
}
}
use
si applica solo allo scope in cui si trovaL’errore del compilatore mostra che il collegamento non si applica più
all’interno del modulo cliente
:
$ cargo build
Compiling ristorante v0.1.0 (file://progetti/ristorante)
error[E0433]: failed to resolve: use of undeclared crate or module `accoglienza`
--> src/lib.rs:11:9
|
11 | accoglienza::aggiungi_in_lista();
| ^^^^^^^^^^^ use of undeclared crate or module `accoglienza`
|
help: consider importing this module through its public re-export
|
10 + use crate::accoglienza;
|
warning: unused import: `crate::sala::accoglienza`
--> src/lib.rs:7:5
|
7 | use crate::sala::accoglienza;
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
For more information about this error, try `rustc --explain E0433`.
warning: `ristorante` (lib) generated 1 warning
error: could not compile `ristorante` (lib) due to 1 previous error; 1 warning emitted
Nota che c’è l’avviso che il use
non è più usato nel suo scope! Per
risolvere questo problema, sposta il use
anche all’interno del modulo
cliente
, o riferisciti al collegamento dal modulo genitore con
super::accoglienza
all’interno del modulo figlio cliente
.
Creare Percorsi use
Idiomatici
Nel Listato 7-11, forse ti sarai chiesto perché abbiamo specificato use crate::sala::accoglienza
e poi chiamato accoglienza::aggiungi_in_lista
in
mangiare_al_ristorante
, invece di specificare il path use
fino alla
funzione aggiungi_in_lista
per ottenere lo stesso risultato, come nel Listato
7-13.
mod sala {
pub mod accoglienza {
pub fn aggiungi_in_lista() {}
}
}
use crate::sala::accoglienza::aggiungi_in_lista;
pub fn mangiare_al_ristorante() {
aggiungi_in_lista();
}
aggiungi_in_lista
nello scope con use
, che non è idiomaticoSebbene sia il Listato 7-11 sia il Listato 7-13 compiano lo stesso compito, il
Listato 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 quando si chiama la funzione rende
chiaro che la funzione non è definita localmente riducendo comunque la
ripetizione del path completo. Il codice in Listato 7-13 non chiarisce dove
sia definita aggiungi_in_lista
.
D’altra parte, quando portiamo struct
, enum
e altri elementi con use
, è
idiomatico specificare il path completo. Il Listato 7-14 mostra il modo
idiomatico per portare, ad esempio, HashMap
della libreria standard nello
scope di un crate binario.
use std::collections::HashMap; fn main() { let mut map = HashMap::new(); map.insert(1, 2); }
HashMap
nello scope in modo idiomaticoNon c’è una ragione forte dietro questo uso: è semplicemente la convenzione che è emersa, e le persone si sono abituate a leggere e scrivere codice Rust in questo modo.
L’eccezione a questa idioma è se stiamo portando due elementi con lo stesso nome
nello scope con use
, perché Rust non lo permette. Il Listato 7-15 mostra
come portare due Result
nello scope che hanno lo stesso nome ma moduli
genitore diversi, e come riferirsi a essi.
use std::fmt;
use std::io;
fn funzione1() -> fmt::Result {
// --taglio--
Ok(())
}
fn funzione2() -> io::Result<()> {
// --taglio--
Ok(())
}
Come si vede, usare i moduli genitore distingue i due type Result
. Se invece
avessimo specificato use std::fmt::Result
e use std::io::Result
, avremmo due
type Result
nello stesso scope, e Rust non saprebbe quale intendessimo
quando avremmo usato Result
.
Fornire Nuovi Nomi con la Parola Chiave as
C’è un’altra soluzione al problema di portare due type con lo stesso nome
nello stesso scope con use
: dopo il path, possiamo specificare as
e un
nuovo nome locale, o alias, per il type. Il Listato 7-16 mostra un altro
modo di scrivere il codice del Listato 7-15 rinominando uno dei due type
Result
con as
.
use std::fmt::Result;
use std::io::Result as IoResult;
fn funzione1() -> Result {
// --taglio--
Ok(())
}
fn funzione2() -> IoResult<()> {
// --taglio--
Ok(())
}
as
Nella seconda dichiarazione use
, abbiamo scelto il nuovo alias IoResult
per il type std::io::Result
, che non entrerà in conflitto con il Result
di
std::fmt
che abbiamo anch’esso portato nello scope. Sia il Listato 7-15 che
il Listato 7-16 sono considerati idiomatici, quindi la scelta spetta a te!
Riesportare Nomi con pub use
Quando portiamo un nome nello scope con la parola chiave use
, il nome è
privato allo scope in cui lo abbiamo importato. Per consentire al codice
esterno a quello scope di riferirsi a quel nome come se fosse stato definito
in quello scope, possiamo combinare pub
e use
. Questa tecnica si chiama
riesportare (re-exporting) perché portiamo un elemento nello scope ma lo
rendiamo anche disponibile affinché altri possano portarlo nel loro scope.
Il Listato 7-17 mostra il codice del Listato 7-11 con use
nella radice del
modulo cambiato in pub use
.
mod sala {
pub mod accoglienza {
pub fn aggiungi_in_lista() {}
}
}
pub use crate::sala::accoglienza;
pub fn mangiare_al_ristorante() {
accoglienza::aggiungi_in_lista();
}
pub use
Prima di questa modifica, il codice esterno avrebbe dovuto chiamare la funzione
aggiungi_in_lista
usando il path
ristorante::sala::accoglienza::aggiungi_in_lista()
, che avrebbe inoltre
richiesto che il modulo sala
fosse marcato come pub
. Ora che questo pub use
ha riesportato il modulo accoglienza
dalla radice del modulo, il codice
esterno può invece usare il path
ristorante::accoglienza::aggiungi_in_lista()
.
La riesportazione è utile quando la struttura interna del tuo codice è diversa
da come i programmatori che chiamano il tuo codice penserebbero al dominio. Per
esempio, in questa metafora del ristorante, chi gestisce il ristorante pensa in
termini di “sala” e “cucine”. Ma i clienti che visitano un ristorante
probabilmente non penseranno alle parti del ristorante in questi termini. Con
pub use
, possiamo scrivere il nostro codice con una struttura e però esporre
una struttura diversa. Ciò rende la nostra libreria ben organizzata sia per i
programmatori che lavorano sulla libreria sia per i programmatori che la usano.
Vedremo un altro esempio di pub use
e di come influisce sulla documentazione
del crate in “Esportare un API Pubblica Efficace” nel Capitolo 14.
Usare Pacchetti Esterni
Nel Capitolo 2, abbiamo programmato un progetto del gioco degli indovinelli che
usava un pacchetto esterno chiamato rand
per ottenere numeri casuali. Per
usare rand
nel nostro progetto, abbiamo aggiunto questa riga a Cargo.toml:
rand = "0.8.5"
Aggiungere rand
come dipendenza in Cargo.toml dice a Cargo di scaricare il
pacchetto rand
e le sue dipendenze da crates.io e di
rendere rand
disponibile al nostro progetto.
Poi, per portare le definizioni di rand
nello scope del nostro pacchetto,
abbiamo aggiunto una riga use
che cominciava con il nome del crate, rand
,
e elencava gli elementi che volevamo portare nello scope. Ricorda che in
“Generare un Numero Casuale” nel Capitolo 2, abbiamo
portato il trait Rng
nello scope e chiamato la funzione
rand::thread_rng
:
use std::io;
use rand::Rng;
fn main() {
println!("Indovina il numero!");
let numero_segreto = rand::thread_rng().gen_range(1..=100);
println!("Il numero segreto è: {numero_segreto}");
println!("Inserisci la tua ipotesi.");
let mut ipotesi = String::new();
io::stdin()
.read_line(&mut ipotesi)
.expect("Errore di lettura");
println!("Hai ipotizzato: {ipotesi}");
}
Membri della community Rust hanno reso molti pacchetti disponibili su
crates.io, e includere uno di essi nel tuo pacchetto
implica gli stessi passi: elencarlo nel file Cargo.toml del tuo pacchetto e
usare use
per portare elementi dai loro crate nello scope.
Nota che la libreria standard std
è anch’essa un crate esterno al nostro
pacchetto. Poiché la libreria standard è distribuita con il linguaggio Rust, non
dobbiamo modificare Cargo.toml per includere std
. Ma dobbiamo comunque
riferirci ad essa con use
per portare elementi da lì nello scope del nostro
pacchetto. Per esempio, per HashMap
useremmo questa riga:
#![allow(unused)] fn main() { use std::collections::HashMap; }
Questo è un path assoluto che inizia con std
, il nome del crate della
libreria standard.
Usare Percorsi Nidificati per Accorpare Elenchi di use
Se usiamo più elementi definiti nello stesso crate o nello stesso modulo,
elencare ogni elemento su una sua riga può occupare molto spazio verticale nei
file. Per esempio, queste due dichiarazioni use
che avevamo nel gioco degli
indovinelli nel Listato 2-4 portano elementi da std
nello scope:
use rand::Rng;
// --taglio--
use std::cmp::Ordering;
use std::io;
// --taglio--
fn main() {
println!("Indovina il numero!");
let numero_segreto = rand::thread_rng().gen_range(1..=100);
println!("Il numero segreto è: {numero_segreto}");
println!("Inserisci la tua ipotesi.");
let mut ipotesi = String::new();
io::stdin()
.read_line(&mut ipotesi)
.expect("Errore di lettura");
println!("Hai ipotizzato: {ipotesi}");
match ipotesi.cmp(&numero_segreto) {
Ordering::Less => println!("Troppo piccolo!"),
Ordering::Greater => println!("Troppo grande!"),
Ordering::Equal => println!("Hai indovinato!"),
}
}
Invece, possiamo usare path nidificati per portare gli stessi elementi nello scope in una sola riga. Lo facciamo specificando la parte comune del path, seguita da due due punti, e poi parentesi graffe intorno a una lista delle parti che differiscono dei path, come mostrato nel Listato 7-18.
use rand::Rng;
// --taglio--
use std::{cmp::Ordering, io};
// --taglio--
fn main() {
println!("Indovina il numero!");
let numero_segreto = rand::thread_rng().gen_range(1..=100);
println!("Il numero segreto è: {numero_segreto}");
println!("Inserisci la tua ipotesi.");
let mut ipotesi = String::new();
io::stdin()
.read_line(&mut ipotesi)
.expect("Errore di lettura");
let ipotesi: u32 = ipotesi.trim().parse().expect("Inserisci un numero!");
println!("Hai ipotizzato: {ipotesi}");
match ipotesi.cmp(&numero_segreto) {
Ordering::Less => println!("Troppo piccolo!"),
Ordering::Greater => println!("Troppo grande!"),
Ordering::Equal => println!("Hai indovinato!"),
}
}
Nei programmi più grandi, portare molti elementi nello scope dallo stesso
crate o modulo usando path nidificati può ridurre di molto il numero di
dichiarazioni use
separate necessarie!
Possiamo usare un path nidificato a qualsiasi livello in un path, il che è
utile quando si combinano due dichiarazioni use
che condividono una parte di
path. Per esempio, il Listato 7-19 mostra due dichiarazioni use
: una che
porta std::io
nello scope e una che porta std::io::Write
nello scope.
use std::io;
use std::io::Write;
use
che condividono parte della pathLa parte comune di questi due path è std::io
, ed è il path completo della
prima path. Per unire questi due path in una sola dichiarazione use
,
possiamo usare self
nel path nidificato, come mostrato nel Listato 7-20.
use std::io::{self, Write};
use
Questa riga porta std::io
e std::io::Write
nello scope.
Importare Elementi con l’Operatore Glob
Se vogliamo portare tutti gli elementi pubblici definiti in un path nello
scope, possiamo specificare quel path seguito dall’operatore glob *
:
#![allow(unused)] fn main() { use std::collections::*; }
Questa dichiarazione use
porta tutti gli elementi pubblici definiti in
std::collections
nello scope corrente. Stai attento quando usi l’operatore
glob! Il glob può rendere più difficile capire quali nomi sono nello scope
e da dove proviene un nome usato nel tuo programma. Inoltre, se la dipendenza
cambia le sue definizioni, ciò che hai importato cambia a sua volta, il che può
portare a errori del compilatore quando aggiorni la dipendenza se la dipendenza
aggiunge una definizione con lo stesso nome di una tua definizione nello stesso
scope, per esempio.
L’operatore glob viene spesso usato durante i test per portare tutto ciò che è
sotto test nel modulo tests
; parleremo di questo in “Come Scrivere dei
Test” nel Capitolo 11. L’operatore glob è anche
a volte usato come parte del pattern prelude: vedi la documentazione della
libreria standard per ulteriori informazioni su quel
pattern.