Utilizarea cuvântului cheie use
pentru a aduce căile în domeniul de vizibilitate
Scrierea completă a căilor pentru apelarea funcțiilor poate fi incomod de repetitivă. În Listarea 7-7, indiferent dacă optăm pentru drumul absolut sau cel relativ către funcția add_to_waitlist
, am trebuit de fiecare dată să specificăm și front_of_house
și hosting
. Din fericire, există o modalitate de a simplifica acest proces: putem crea o scurtătură către o cale cu ajutorul cuvântului cheie use
. Astfel, putem folosi un nume mai scurt în restul domeniului de vizibilitate.
În Listarea 7-11, introducem modulul crate::front_of_house::hosting
în domeniul de vizibilitate al funcției eat_at_restaurant
. Astfel, pentru apelarea funcției add_to_waitlist
în contextul eat_at_restaurant
, trebuie doar să specificăm hosting::add_to_waitlist
.
Numele fișierului: src/lib.rs
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();
}
Listarea 7-11: Introducerea unui modul în domeniul de vizibilitate cu use
Adăugarea use
și a unei căi într-un domeniu este similară cu operațiunea de creare a unui link simbolic în sistemul de fișiere. Prin introducerea use crate::front_of_house::hosting
la nivelul rădăcinii crate-ului, hosting
devine un nume valid în cadrul acestui domeniu de vizibilitate, ca și cum modulul hosting
ar fi fost definit chiar în rădăcina crate-ului. Căile aduse în vizibilitate cu use
au capacitatea de a verifica confidențialitatea, la fel ca toate celelalte căi.
Trebuie să ținem minte că use
creează o scurtătură doar în cadrul specific al domeniului de vizibilitate în care este folosit. Listarea 7-12 plasează funcția eat_at_restaurant
într-un nou submodul numit customer
. Acesta reprezintă un domeniu diferit de cel al declarației use
, așa că funcția nu va putea fi compilată:
Filename: src/lib.rs
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();
}
}
Listarea 7-12: Instrucțiunea use
este aplicabilă doar în domeniul de vizibilitate în care se găsește
Eroarea de compilare indică faptul că scurtătura nu mai este valabilă în interiorul modulului customer
:
$ 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
Este important de observat că există de asemenea un avertisment care ne informează că instrucțiunea use
nu mai este folosită în domeniul său de vizibilitate! Pentru a remediat această problemă, putem muta instrucțiunea use
direct în interiorul modulului customer
sau putem face referire la scurtătură din modulul părinte folosind super::hosting
, în interiorul modulului copil customer
.
Căi use
idiomatice
E posibil să te fi întrebat, privind Listarea 7-11, de ce am utilizat use crate::front_of_house::hosting
și apoi am apelat funcția hosting::add_to_waitlist
în cadrul funcției eat_at_restaurant
. De ce nu am specificat calea completă în use
până la funcția add_to_waitlist
pentru a obține același rezultat? Acest ultim mod de a proceda este ilustrat în Listarea 7-13.
Numele fișierului: src/lib.rs
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();
}
Listarea 7-13: Introducerea funcției add_to_waitlist
în domeniul de vizibilitate cu use
, o abordare care nu respectă normele idiomatice ale limbajului
Deși ambele metode, ilustrate în Listările 7-11 și 7-13, îndeplinesc aceeași sarcină, abordarea din Listarea 7-11 este considerată a fi cea corectă, conform obiceiurilor limbajului. Utilizarea lui use
pentru a introduce modulul părinte al funcției în domeniul de vizibilitate necesită specificarea modulului părinte când apelăm funcția. Însă, astfel, devine evident că funcția nu este definită local, minimizând în același timp repetarea căii complete. În schimb, codul din Listarea 7-13 lasă în incertitudine locul unde add_to_waitlist
este definită.
Cu toate acestea, când introducem în domeniul de vizibilitate structuri, enumerări și alte elemente cu ajutorul lui use
, este idiomatic să se specifice calea completă. Listarea 7-14 arată metoda adecvată de a aduce structura HashMap
din biblioteca standard în domeniul de vizibilitate al unui crate binar.
Numele fișierului: src/main.rs
use std::collections::HashMap; fn main() { let mut map = HashMap::new(); map.insert(1, 2); }
Listarea 7-14: Introducerea HashMap
în domeniu de vizibilitate în modul consecvent
Această idiomă nu are o cauză anume, este doar o tradiție care a fost adoptată pentru scrierea și citirea codului Rust.
Excepția la această convenție este dacă aducem în domeniul de vizibilitate două elemente care au același nume, folosind declarațiile use
. Rust nu permite asta. Listarea 7-15 ilustrează cum să introducem în domeniul de vizibilitate două tipuri Result
cu același nume, dar provenind din module părinte diferite și cum să le referim.
Numele fișierului: src/lib.rs
use std::fmt;
use std::io;
fn function1() -> fmt::Result {
// --snip--
Ok(())
}
fn function2() -> io::Result<()> {
// --snip--
Ok(())
}
Listarea 7-15: Introducerea simultană a două tipuri cu același nume în același domeniu de vizibilitate impune utilizarea modulelor părinte.
După cum poți observa, specificarea modulelor părinte ne ajută să distingem între cele două tipuri Result
. Dacă am fi folosit use std::fmt::Result
și use std::io::Result
, am fi ajuns cu două tipuri Result
în același domeniu și Rust nu ar fi putut distinge la care tip Result
ne referim.
Utilizarea cuvântului cheie as
pentru a atribui nume noi
O altă soluție la problema aducerii a două tipuri cu același nume în același domeniu de vizibilitate cu use
implică folosirea cuvântului cheie as
. După ce specificăm calea, putem adăuga as
și un nume local nou, sau un alias, pentru tipul respectiv. Listarea 7-16 ne prezintă o altă variantă de a scrie codul din Listarea 7-15 prin redenumirea unuia dintre cele două tipuri Result
folosind as
.
Numele fișierului: src/lib.rs
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
Ok(())
}
fn function2() -> IoResult<()> {
// --snip--
Ok(())
}
Listarea 7-16: Redenumirea unui tip la momentul introducerii acestuia în domeniul de vizibilitate, folosind cuvântul cheie as
În cel de-al doilea enunț use
, noi am decis să alegem numele IoResult
pentru tipul std::io::Result
. Acesta nu va intra în conflict cu tipul Result
din std::fmt
pe care tot l-am adus în domeniul de vizibilitate. Atât Listarea 7-15 cât și Listarea 7-16 reprezintă abordări idiomatice, deci ai libertatea de a alege cea care ți se potrivește cel mai bine!
Re-exportarea numelor cu pub use
Când aducem un nume în domeniul de vizibilitate cu ajutorul cuvântului cheie use
, numele disponibil în noul domeniu de vizibilitate este privat. Pentru a permite codului care apelează codul nostru să se refere la acel nume ca și cum ar fi fost definit în domeniul de vizibilitate al acelui cod, putem combina pub
și use
. Această tehnică se numește re-exportare deoarece aducem un element în domeniul de vizibilitate, dar de asemenea face acel element disponibil și pentru cei ce importă codul nostru.
Listarea 7-17 prezintă codul din Listarea 7-11 cu use
în modulul root modificat în pub use
.
Numele fișierului: src/lib.rs
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();
}
Listarea 7-17: Facem un nume disponibil pentru orice cod de utilizat dintr-un nou domeniu de vizibilitate cu pub use
Înainte de această modificare, codul extern ar trebui să apeleze funcția add_to_waitlist
utilizând calea restaurant::front_of_house::hosting::add_to_waitlist()
. Acum, având în vedere că pub use
a re-exportat modulul hosting
din modulul root, codul extern poate acum utiliza calea restaurant::hosting::add_to_waitlist()
în schimb.
Re-exportarea este utilă când structura internă a codului tău este diferită de felul în care programatorii care apelează codul tău ar gândi despre domeniu. De exemplu, în această metaforă despre restaurant, oamenii care administrează restaurantul gândesc despre "fața casei" și "spatele casei". Dar clienții care vizitează un restaurant probabil nu vor gândi despre părțile restaurantului în acești termeni. Cu pub use
, putem scrie codul nostru cu o structură, dar expunem o structură diferită. Făcând acest lucru, biblioteca noastră este bine organizată atât pentru programatorii care lucrează în bibliotecă cât și pentru programatorii care apelează biblioteca. Vom privi un alt exemplu de pub use
și cum acesta afectează documentația crate-ului tău în secțiunea „Exportarea unui API public și accesibil cu pub use
” din Capitolul 14.
Utilizarea pachetelor externe
În capitolul 2, am creat un joc de ghicit numere, care se baza pe un pachet extern numit rand
pentru generarea numerelor aleatorii. Pentru a folosi rand
în cadrul proiectului nostru, am inclus următoarea linie în fișierul Cargo.toml:
Numele fișierului: Cargo.toml
rand = "0.8.5"
Adăugarea rand
ca o dependență în fișierul Cargo.toml instruiește sistemul Cargo să descarce pachetul rand
și orice dependențe asociate de pe crates.io și să-l predea la dispoziția proiectului nostru.
Ulterior, pentru a aduce definițiile din rand
în domeniul de vizibilitate al pachetului nostru, am inclus o instrucțiune use
. Aceasta era precedată de numele pachetului, rand
, și continuată cu lista elementelor pe care am dorit să le aducem în vizibilitate. Îți poți aduce aminte de secțiunea „Generarea unui număr aleator” din capitolul 2, unde am introdus trăsătura Rng
în domeniul de vizibilitate și am apelat funcția rand::thread_rng
:
use std::io;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {guess}");
}
Membrii comunității Rust au pus la dispoziție multe pachete pe crates.io, iar integrarea oricăruia dintre acestea în pachetul tău presupune aceeași pași: listarea pachetelor în fișierul Cargo.toml și utilizarea instrucțiunii use
pentru a introduce elemente din aceste pachete în domeniul de vizibilitate.
Este important de notat că și biblioteca standard std
este de asemenea un crate extern pachetului nostru. Totuși, deoarece biblioteca standard este inclusă în setul de livrare standard al limbajului Rust, nu trebuie să modificăm Cargo.toml pentru a include std
. Trebuie doar să folosim instrucțiunea use
pentru a introduce elemente din aceasta în domeniul de vizibilitate al pachetului nostru. De exemplu, pentru a folosi HashMap
, am utiliza următoarea linie:
#![allow(unused)] fn main() { use std::collections::HashMap; }
Aceasta este o cale absolută care începe cu std
, numele crate-ului bibliotecii standard.
Utilizarea căilor îmbinate pentru a economisi spațiu în listele lungi de use
Când lucrăm cu numeroase elemente definite în același crate sau în același modul, este obositor și consumator de spațiu să le listăm pe fiecare în parte pe linii separate. Să luăm ca exemplu jocul Ghicitoarea din Listarea 2-4, unde am folosit două declarații use
pentru a importa elemente din std
în domeniul nostru de vizibilitate:
Numele fișierului: src/main.rs
use rand::Rng;
// --snip--
use std::cmp::Ordering;
use std::io;
// --snip--
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}
În loc de aceasta, putem utiliza căi îmbinate pentru a aduce aceleași elemente în domeniul de vizibilitate într-o singură linie. Acest lucru se face prin specificarea părții comune a căii, continuată cu două puncte și cu acolade ce îngrădesc părțile diferite ale căilor. Acest concept este prezentat in Listarea 7-18.
Numele fișierului: src/main.rs
use rand::Rng;
// --snip--
use std::{cmp::Ordering, io};
// --snip--
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = guess.trim().parse().expect("Please type a number!");
println!("You guessed: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}
Listarea 7-18: Utilizarea unei căi îmbinate pentru a importa mai multe elemente cu același prefix în domeniul de vizibilitate
În cadrul programelor mari, tehnica importării unui număr mare de elemente din același crate sau modul prin căi îmbinate poate reduce considerabil numărul de declarații use
separate.
O cale îmbinată poate fi utilizată la orice nivel într-o cale, fapt util atunci când dorim să combinăm două declarații use
care au o sub-cale comună. De exemplu, Lista 7-19 ne arată două declarații use
: una ce aduce std::io
în domeniul de vizibilitate și una care aduce std::io::Write
în domeniul de vizibilitate.
Numele fișierului: src/lib.rs
use std::io;
use std::io::Write;
Listarea 7-19: Două declarații use
cu o sub-cale comună
Pentru a combina cele două căi într-o singură declarație use
, folosim self
în cadrul căii îmbinate, așa cum vedem în Listarea 7-20.
Numele fișierului: src/lib.rs
use std::io::{self, Write};
Listarea 7-20: Combinarea căilor din Lista 7-19 într-o singură declarație use
În urma acestei operațiuni, std::io
și std::io::Write
sunt aduse în domeniul de vizibilitate.
Utilizarea operatorului *
(glob)
Daca dorim să includem toate elementele publice definite printr-o anumită cale în domeniul nostru de vizibilitate, atunci trebuie sa utilizăm calea dorită completată cu operatorul *
:
#![allow(unused)] fn main() { use std::collections::*; }
Această instrucțiune use
face ca toate elementele publice definite în std::collections
să fie disponibile în domeniul de vizibilitate actual. Trebuie să fim prudenți când utilizăm operatorul *
! Ne poate face dificilă identificarea numelor care sunt în domeniul de vizibilitate și localizarea locului unde a fost definit un nume folosit în programul nostru.
De obicei, utilizăm operatorul *
atunci când realizăm teste pentru a include totul în modulul tests
. Vom aborda acest subiect în secțiunea „Cum să scriem teste” din Capitolul 11. De asemenea, uneori operatorul *
face parte din pattern-ul 'preludiu'. Poți accesa documentația bibliotecii standard pentru a afla mai multe detalii despre acest pattern.