Uruchamianie kodu podczas czyszczenia za pomocą cechy Drop
Druga cecha ważna dla wzorca wskaźnika sprytnego to Drop, która pozwala na
dostosowanie tego, co dzieje się, gdy wartość ma wyjść poza zakres. Możesz
dostarczyć implementację cechy Drop dla dowolnego typu, a ten kod może
zostać użyty do zwolnienia zasobów, takich jak pliki lub połączenia sieciowe.
Wprowadzamy Drop w kontekście wskaźników sprytnych, ponieważ funkcjonalność
cechy Drop jest prawie zawsze używana podczas implementacji wskaźnika
sprytnego. Na przykład, gdy Box<T> zostanie usunięty (dropped), zwolni
przestrzeń na stercie, na którą wskazuje Box.
W niektórych językach, dla niektórych typów, programista musi wywołać kod, aby zwolnić pamięć lub zasoby za każdym razem, gdy skończy używać instancji tych typów. Przykładami są uchwyty plików, gniazda sieciowe i blokady. Jeśli programista zapomni, system może zostać przeciążony i ulec awarii. W Rust można określić, że dany fragment kodu ma być uruchamiany za każdym razem, gdy wartość wyjdzie poza zakres, a kompilator wstawi ten kod automatycznie. W rezultacie nie musisz martwić się o umieszczanie kodu czyszczącego wszędzie w programie, gdzie instancja określonego typu zostaje zakończona – nadal nie będziesz miał wycieków zasobów!
Kod do uruchomienia, gdy wartość wyjdzie poza zakres, określasz, implementując
cechę Drop. Cecha Drop wymaga zaimplementowania jednej metody o nazwie
drop, która przyjmuje mutowalną referencję do self. Aby zobaczyć, kiedy Rust
wywołuje drop, zaimplementujmy na razie drop z instrukcjami println!.
Listing 15-14 przedstawia strukturę CustomSmartPointer, której jedyną
dostosowaną funkcjonalnością jest to, że wypisze Dropping CustomSmartPointer!,
gdy instancja wyjdzie poza zakres, aby pokazać, kiedy Rust uruchamia metodę
drop.
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");
}
Cecha Drop jest uwzględniona w preambule, więc nie musimy jej importować.
Implementujemy cechę Drop dla CustomSmartPointer i dostarczamy implementację
metody drop, która wywołuje println!. Treść metody drop to miejsce, w
którym umieściłbyś dowolną logikę, którą chciałbyś uruchomić, gdy instancja
twojego typu wyjdzie poza zakres. Wypisujemy tutaj tekst, aby wizualnie
zademonstrować, kiedy Rust wywoła drop.
W main tworzymy dwie instancje CustomSmartPointer, a następnie wypisujemy
CustomSmartPointers created. Pod koniec main, nasze instancje
CustomSmartPointer wyjdą poza zakres, a Rust wywoła kod, który umieściliśmy
w metodzie drop, wypisując naszą końcową wiadomość. Zauważ, że nie musieliśmy
jawnie wywoływać metody drop.
Po uruchomieniu tego programu zobaczymy następujący wynik:
$ cargo run
Compiling drop-example v0.1.0 (file:///projects/drop-example)
Finished `dev` profile [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 automatycznie wywołał drop dla nas, gdy nasze instancje wyszły poza
zakres, uruchamiając określony przez nas kod. Zmienne są usuwane w odwrotnej
kolejności ich tworzenia, więc d zostało usunięte przed c. Celem tego
przykładu jest zapewnienie wizualnego przewodnika po działaniu metody drop;
zazwyczaj określiłbyś kod czyszczący, który twój typ musi uruchomić, a nie
komunikat do wypisania.
Niestety, wyłączenie automatycznej funkcjonalności drop nie jest proste.
Wyłączanie drop zazwyczaj nie jest konieczne; cały sens cechy Drop polega
na tym, że jest obsługiwana automatycznie. Czasami jednak możesz chcieć
posprzątać wartość wcześniej. Jednym z przykładów jest użycie wskaźników
sprytnych zarządzających blokadami: Możesz chcieć wymusić wywołanie metody
drop, która zwalnia blokadę, tak aby inny kod w tym samym zakresie mógł
nabyć blokadę. Rust nie pozwala na ręczne wywołanie metody drop cechy Drop;
zamiast tego musisz wywołać funkcję std::mem::drop dostarczoną przez
bibliotekę standardową, jeśli chcesz wymusić usunięcie wartości przed końcem
jej zakresu.
Próba ręcznego wywołania metody drop cechy Drop poprzez modyfikację funkcji
main z Listingu 15-14 nie zadziała, jak pokazano w Listingu 15-15.
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");
}
Kiedy spróbujemy skompilować ten kod, otrzymamy taki błąd:
$ 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
|
16 - c.drop();
16 + drop(c);
|
For more information about this error, try `rustc --explain E0040`.
error: could not compile `drop-example` (bin "drop-example") due to 1 previous error
Ten komunikat o błędzie informuje, że nie wolno nam jawnie wywoływać drop.
Komunikat o błędzie używa terminu destruktor, który jest ogólnym terminem
programistycznym dla funkcji, która oczyszcza instancję. Destruktor jest
analogiczny do konstruktora, który tworzy instancję. Funkcja drop w Rust
jest jednym szczególnym destruktorem.
Rust nie pozwala nam jawnie wywoływać drop, ponieważ Rust nadal
automatycznie wywoływałby drop na wartości na końcu main. Spowodowałoby to
błąd podwójnego zwolnienia pamięci, ponieważ Rust próbowałby dwukrotnie
posprzątać tę samą wartość.
Nie możemy wyłączyć automatycznego wstawiania drop, gdy wartość wyjdzie poza
zakres, i nie możemy jawnie wywołać metody drop. Więc, jeśli potrzebujemy
wymusić wczesne posprzątanie wartości, używamy funkcji std::mem::drop.
Funkcja std::mem::drop różni się od metody drop w cechy Drop. Wywołujemy
ją, przekazując jako argument wartość, którą chcemy wymusić usunięcie.
Funkcja znajduje się w preambule, więc możemy zmodyfikować main w Listingu
15-15, aby wywołać funkcję drop, jak pokazano w Listingu 15-16.
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");
}
Uruchomienie tego kodu wypisze następujące:
$ cargo run
Compiling drop-example v0.1.0 (file:///projects/drop-example)
Finished `dev` profile [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
Tekst Dropping CustomSmartPointer with data `some data`! jest wypisany
pomiędzy tekstami CustomSmartPointer created i CustomSmartPointer dropped before the end of main, pokazując, że kod metody drop jest wywoływany w celu
usunięcia c w tym punkcie.
Kod określony w implementacji cechy Drop można wykorzystać na wiele sposobów,
aby czyszczenie było wygodne i bezpieczne: na przykład można go użyć do
stworzenia własnego alokatora pamięci! Dzięki cechy Drop i systemowi własności
Rust nie musisz pamiętać o czyszczeniu, ponieważ Rust robi to automatycznie.
Nie musisz też martwić się problemami wynikającymi z przypadkowego czyszczenia
wartości nadal używanych: System własności, który zapewnia, że referencje są
zawsze prawidłowe, gwarantuje również, że drop zostanie wywołany tylko raz,
gdy wartość nie jest już używana.
Teraz, gdy przeanalizowaliśmy Box<T> i niektóre cechy wskaźników sprytnych,
przyjrzyjmy się kilku innym wskaźnikom sprytnym zdefiniowanym w bibliotece
standardowej.