Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.