Historia pytania:
W Rust zarządzanie zasobami bez zbieracza śmieci stało się możliwe dzięki surowym zasadom własności i cyklowi życia obiektów. Aby zautomatyzować zwalnianie zasobów (np. deskryptorów plików, gniazd, pamięci z bibliotek zewnętrznych), od samego początku rozwoju języka wprowadzono trait Drop. To główny sposób „reakcji” na zakończenie życia obiektu, który jest stosowany do finalizacji i zwracania zasobów systemowi operacyjnemu lub zwalniania pamięci.
Problem:
Zwykłe typy Rust automatycznie oczyszczają swoje zasoby, ale kiedy struktura przechowuje zasób wymagający ręcznego zwolnienia (np. otwarty plik lub niebezpieczny wskaźnik), niechęć lub zapomnienie programisty mogą powodować wycieki lub wyścigi zasobów. Jeśli Drop jest źle zaimplementowany (np. nie uwzględniono możliwości podwójnego zwolnienia pamięci), może to prowadzić do błędów czasowych wykonania.
Rozwiązanie:
Trait Drop pozwala zdefiniować specjalną metodę drop(&mut self), która jest automatycznie wywoływana przy niszczeniu wartości. Nadaje się do zwolnienia zasobów dokładnie wtedy, gdy obiekt wychodzi poza zasięg widoczności. Ważne jest, aby pamiętać, że zasobów (np. zamykania pliku) należy zwalniać tylko tutaj.
Przykład kodu:
struct RawFile { handle: *mut libc::FILE, } impl Drop for RawFile { fn drop(&mut self) { if !self.handle.is_null() { unsafe { libc::fclose(self.handle); } } } }
Kluczowe cechy:
Czy można jawnie wywołać drop dla obiektu, aby zwolnić zasób przed czasem?
Nie, bezpośrednie wywołanie metody drop (&obj.drop()) jest zabronione — tylko przez funkcję std::mem::drop(obj), która przejmuje własność obiektu i automatycznie wywołuje drop. Bezpośrednie wywołanie drop() nie kompiluje się.
Przykład kodu:
fn main() { let f = File::open("foo.txt").unwrap(); // drop(&mut f); // Błąd kompilacji! std::mem::drop(f); // Poprawnie: zamknięcie pliku }
Co się stanie, jeśli struktura z Drop znajdzie się pod memcpy lub move? Czy destruktor nie wywoła się dwa razy?
Nie, destruktor zawsze jest wywoływany dokładnie raz w całym cyklu życia obiektu, a kompilator pilnuje tego. Ale jeśli dokonamy niebezpiecznego kopiowania „surowych” bajtów struktury lub użyjemy mem::forget, drop może w ogóle się nie wywołać — stąd niebezpieczeństwo.
Czy można zaimplementować Drop i Copy jednocześnie dla tego samego typu?
Nie, kompilator zabrania tej kombinacji: typ, który realizuje Drop, nie może być Copy, aby zapewnić jednorazowe wywołanie destruktora i wykluczyć podwójne zwolnienie zasobu.
Programista napisał prosty wrapper do pracy z otwartym plikiem, ale zapomniał zaimplementować Drop i zamknąć deskryptor przy usuwaniu obiektu.
Plusy:
Minusy:
Programista zaimplementował Drop dla wrappera deskryptora pliku, jawnie zamykając plik w drop. Teraz, kiedy jakakolwiek zmienna tej struktury wychodzi z funkcji lub panic, zasób jest gwarantowanie zwalniany.
Plusy:
Minusy: