Executarea codului în etapa de curățare cu trăsătura Drop
A doua trăsătură esențială pentru design-ul pointerilor inteligenți este Drop
, care îți oferă posibilitatea de a personaliza acțiunile ce au loc atunci când o valoare va ieși din domeniul de vizibilitate. Ai posibilitatea să implementezi trăsătura Drop
pentru orice tip, iar codul respectiv poate fi utilizat pentru a elibera resurse, cum ar fi fișiere sau conexiuni de rețea.
Vorbim despre Drop
în contextul pointerilor inteligenți pentru că, de obicei, funcționalitatea asociată cu trăsătura Drop
este folosită în cadrul implementării unui pointer inteligent. De exemplu, atunci când un Box<T>
este descărcat, el va dealoca spațiul pe heap la care se referă acesta.
În alte limbaje de programare, pentru anumite tipuri de date, dezvoltatorul trebuie să execute manual cod pentru a elibera memoria sau resursele de fiecare dată când termină de utilizat o instanță a acestor tipuri. Sunt incluse cazuri precum descriptorii de fișiere, socket-uri sau blocările de resurse. Dacă ar omite, sistemul ar putea deveni supraîncărcat și ar putea cădea. În Rust, poți specifica un anumit segment de cod care să fie rulat când o valoare părăsește domeniul de vizibilitate, iar compilatorul va insera acest segment de cod automat. Astfel, nu ești nevoit să introduci cod de curățare oriunde într-un program doar pentru că ai terminat de folosit o instanță de un anumit tip — și nu vei pierde resurse!
Specifici codul care urmează să fie executat când o valoare iese din domeniul de vizibilitate implementând trăsătura Drop
. Drop
necesită implementarea unei metode denumite drop
care primește o referință mutabilă către self
. Pentru a vedea momentul în care Rust invocă drop
, să implementăm momentan metoda drop
cu instrucțiuni println!
.
Listarea 15-14 prezintă structura CustomSmartPointer
care, prin unica ei funcționalitate particulară, va afișa mesajul Dropping CustomSmartPointer!
la ieșirea instanței din domeniul de vizibilitate, demonstrând astfel momentul în care Rust execută funcția drop
.
Filename: src/main.rs
struct CustomSmartPointer { data: String, } impl Drop for CustomSmartPointer { fn drop(&mut self) { println!("Dropping CustomSmartPointer with data `{}`!", self.data); } } fn main() { let c = CustomSmartPointer { data: String::from("my stuff"), }; let d = CustomSmartPointer { data: String::from("other stuff"), }; println!("CustomSmartPointers created."); }
Listarea 15-14: Structura CustomSmartPointer
implementând trăsătura Drop
, unde am plasa codul nostru de curățare
Trăsătura Drop
este inclusă în preludiu, prin urmare, nu este nevoie să o facem vizibilă în domeniul de aplicabilitate. Implementăm trăsătura Drop
pe CustomSmartPointer
și oferim o implementare pentru metoda drop
care invocă macro-ul println!
. În corpul funcției drop
ai include logica pe care dorești să o executi când o instanță a tipului tău este pe cale să iasă din domeniul de vizibilitate. În exemplul nostru, afișăm un text pentru a arăta vizual momentul la care Rust va chema drop
.
În funcția main
, construim două instanțe de CustomSmartPointer
și apoi afișăm CustomSmartPointers created
. La sfârșitul main
, instanțele noastre de CustomSmartPointer
vor ieși din domeniu de vizibilitate, și Rust va chema codul pe care l-am pus în metoda drop
, imprimând mesajul final. Este de notat că nu e necesar să chemăm metoda drop
în mod direct.
Când rulăm acest program, se va afișa următorul rezultat:
$ cargo run
Compiling drop-example v0.1.0 (file:///projects/drop-example)
Finished dev [unoptimized + debuginfo] target(s) in 0.60s
Running `target/debug/drop-example`
CustomSmartPointers created.
Dropping CustomSmartPointer with data `other stuff`!
Dropping CustomSmartPointer with data `my stuff`!
Rust a chemat automat drop
pentru noi când instanțele noastre au ieșit din domeniu de aplicabilitate, executând codul pe care l-am definit. Variabilele sunt eliberate în ordinea inversă creării lor, așadar d
a fost eliberată înaintea lui c
. Scopul acestor exemple este să îți ofere o ilustrare vizuală a modului în care acționează metoda drop
; în mod normal ai seta codul de curățare necesar tipului tău, în loc de un mesaj tipărit.
Eliberarea anticipată a unei valori folosind std::mem::drop
Din păcate, nu este un proces simplu să dezactivezi funcționalitatea automată de drop
. De altfel, dezactivarea drop
nu este necesară de obicei; esența trăsăturii Drop
este aceea că este gestionată în mod automat. Totuși, uneori s-ar putea să vrei să eliberezi o valoare mai devreme. Un exemplu ar fi utilizarea pointerilor inteligenți care controlează lock-uri (instrucțiunea lock e o directivă de bază a programării concurente): s-ar putea să vrei să forțezi metoda drop
care eliberează un lock, astfel încât alt cod din același domeniu de vizibilitate să-l poată prelua. Rust nu îți permite să apelezi manual metoda drop
a trăsăturii Drop
; în loc de aceasta trebuie să folosești funcția std::mem::drop
oferită de biblioteca standard, când dorești să forțezi eliberarea unei valori înainte de terminarea domeniului său de vizibilitate.
Dacă încercăm să apelăm manual metoda drop
a trăsăturii Drop
prin modificarea funcției main
din Listarea 15-14, așa cum este arătat în Listarea 15-15, vom întâmpina o eroare de compilare:
Numele fișierului: src/main.rs
struct CustomSmartPointer {
data: String,
}
impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}
fn main() {
let c = CustomSmartPointer {
data: String::from("some data"),
};
println!("CustomSmartPointer created.");
c.drop();
println!("CustomSmartPointer dropped before the end of main.");
}
Listarea 15-15: Încercarea de a invoca manual metoda drop
din trăsătura Drop
pentru a realiza o curățare prematură
Când încercăm să compilăm acest cod, vom primi următoarea eroare:
$ cargo run
Compiling drop-example v0.1.0 (file:///projects/drop-example)
error[E0040]: explicit use of destructor method
--> src/main.rs:16:7
|
16 | c.drop();
| --^^^^--
| | |
| | explicit destructor calls not allowed
| help: consider using `drop` function: `drop(c)`
For more information about this error, try `rustc --explain E0040`.
error: could not compile `drop-example` due to previous error
Acest mesaj de eroare ne spune că nu ne este permis să apelăm explicit drop
. Mesajul de eroare folosește termenul destructor, ceea ce în terminologia programării se referă la o funcție care face curățenie după o instanță. Destructorul este analog cu constructorul, care inițiază o instanță. Funcția drop
din Rust este un exemplu de destructor.
Rust nu ne permite să apelăm metoda drop
în mod explicit pentru că Rust ar apela automat metoda drop
pentru valoarea respectivă la terminarea funcției main
. Acest lucru ar duce la o eroare de eliberare dublă (double free), deoarece Rust ar încerca să curețe aceeași valoare de două ori.
Nu putem dezactiva inserția automată a metodei drop
atunci când o valoare iese din domeniu de vizibilitate, și nici să apelăm explicit metoda drop
. Astfel, dacă dorim să forțăm curățarea unei valori mai devreme, trebuie să utilizăm funcția std::mem::drop
.
Funcția std::mem::drop
diferă de metoda drop
din trăsătura Drop
. Pentru a o apela, pasăm ca argument valoarea pe care vrem să o ștergem forțat. Această funcție este inclusă în preludiu, astfel că putem modifica funcția main
din Listarea 15-15 pentru a apela funcția drop
, așa cum se arată în Listarea 15-16:
Numele fișierului: src/main.rs
struct CustomSmartPointer { data: String, } impl Drop for CustomSmartPointer { fn drop(&mut self) { println!("Dropping CustomSmartPointer with data `{}`!", self.data); } } fn main() { let c = CustomSmartPointer { data: String::from("some data"), }; println!("CustomSmartPointer created."); drop(c); println!("CustomSmartPointer dropped before the end of main."); }
Listarea 15-16: Apelând funcția std::mem::drop
pentru a elibera explicit o valoare înainte de a ieși din domeniu de vizibilitate
Executând acest cod va genera următoarea afișare:
$ cargo run
Compiling drop-example v0.1.0 (file:///projects/drop-example)
Finished dev [unoptimized + debuginfo] target(s) in 0.73s
Running `target/debug/drop-example`
CustomSmartPointer created.
Dropping CustomSmartPointer with data `some data`!
CustomSmartPointer dropped before the end of main.
Textul 'Dropping CustomSmartPointer with data `some data`!'
este afișat între 'CustomSmartPointer created.'
și 'CustomSmartPointer dropped before the end of main.'
, indicând faptul că codul metodei drop
este executat pentru a elibera c
la acel moment.
Putem utiliza codul specificat într-o implementare a trăsăturii Drop
în diverse moduri pentru a asigura o curățare convenabilă și sigură: de exemplu, am putea să îl folosim pentru a dezvolta un propriul nostru alocator de memorie! Beneficiind de trăsătura Drop
și de sistemul de posesiune al limbajului Rust, nu trebuie să ne amintim să realizăm curățarea deoarece Rust o face în mod automat.
De asemenea, nu trebuie să ne îngrijorăm referitor la problemele care pot apărea din cauza eliberării accidentale a valorilor încă în folosință: sistemul de posesiune, care se asigură în permanență că referințele sunt valide, mai și garantează că drop
este apelat doar o singură dată, când valoarea nu mai este utilizată.
Acum, după ce am investigat Box<T>
și unele din caracteristicile pointerilor inteligenți, să explorăm câțiva alți pointeri inteligenți definiți în biblioteca standard.