Виконання Коду при Очищенні з Трейтом Drop
Другий важливий трейт для шаблону розумного вказівника - Drop
, який дозволяє вам налаштувати, що відбувається, коли значення збирається вийти з області видимості. Ви можете створити реалізацію трейту Drop
для будь-якого типу, і цей код може використовуватися для звільнення ресурсів на кшталт файлів чи мережевих з'єднань.
Ми презентуємо Drop
у контексті розумних вказівників, оскільки функціональність трейту Drop
практично завжди використовується при реалізації розумних вказівників. Наприклад, коли Box<T>
скидається, то звільняє простір у купі, виділений при створенні.
У деяких мовах для деяких типів програміст повинен викликати код для звільнення пам'яті або ресурсів кожного разу, коли він завершує використовувати екземпляр такого типу. Прикладами можуть бути файли, сокети чи блокування доступу до даних. Якщо програміст забуде це зробити, система може перенавантажитися і впасти. У Rust ви можете вказати, що спеціальний шматок коду має бути виконано, коли значення виходить з зони видимості, і компілятор додасть цей код автоматично. В результаті вам не треба стежити за ретельним розміщенням коду очищення всюди в програмі для завершення роботи з екземпляром певного типу - і все одно ви не допустите витоку ресурсів!
Ви вказуєте код, що потрібно виконати, коли значення виходить з області видимості, реалізуючи трейт Drop
. Трейт Drop
потребує реалізації одного методу, що зветься drop
, який приймає мутабельне посилання на self
. Щоб побачити, коли Rust викликає drop
, тимчасово реалізуймо drop
з інструкціями println!
.
Блок коду 15-14 показує структуру CustomSmartPointer
, чия єдина особлива функціональність полягає в тому, що вона виводить Dropping CustomSmartPointer!
, коли екземпляр виходить із зони видимості, щоб показати, коли Rust запускає функцію drop
.
Файл: 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."); }
Трейт Drop
включено до прелюдії, тож нам не треба вводити її в область видимості. Ми реалізуємо трейт Drop
для CustomSmartPointer
і надаємо реалізацію методу drop
, що викликає println!
. Саме у тілі функції drop
треба розмістити логіку, яку ви хочете виконати, коли екземпляр типу виходить з області видимості. Тут ми виводимо деякий текст для наочної демонстрації, коли саме Rust викличе drop
.
У main
ми створимо два екземпляри CustomSmartPointer
і виведемо CustomSmartPointers created
. Наприкінці main
наші екземпляри CustomSmartPointer
вийдуть з області видимості, і Rust викличе код, який ми розмістили у методі drop
, вивівши наше останнє повідомлення. Зверніть увагу що нам не треба явно викликати метод drop
.
Коли ми запустимо цю програму, то побачимо таке:
$ 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 автоматично викликав для нас drop
, коли наші екземпляри вийшли з області видимості, виконавши зазначений код. Змінні очищуються у зворотному порядку від створення, тож d
буде очищено перед c
. Мета цього прикладу - надати Вам візуальний посібник того, як працює метод drop
; зазвичай ви вказуєте код для очищення, який треба запустити вашому типу, а не друкуєте повідомлення.
Завчасне Очищення Значення з std::mem::drop
На жаль, зовсім не очевидно, як вимкнути автоматичну функціональність drop
. Відключати drop
зазвичай не потрібно; весь сенс трейту Drop
полягає в тому, що про нього компілятор дбає автоматично. Однак, іноді ви можете захотіти очистити значення завчасно. Візьмемо такий приклад: розумні вказівники, що керують блокуванням; ви можете захотіти примусово запустити метод drop
, що відпускає блокування, щоб інший код у цій області видимості міг захопити це блокування. Rust не дозволяє вам викликати метод drop
трейту Drop
вручну; натомість ви маєте викликати функцію std::mem::drop
, надану стандартною бібліотекою, якщо ви хочете примусово очистити значення перед до закінчення її області видимості.
Якщо ми спробуємо викликати метод drop
трейту Drop
вручну, змінивши функцію main
з Блоку коду 15-14, як показано у Блоці коду 15-15, то отримаємо помилку компілятора:
Файл: 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.");
}
Якщо ми спробуємо скомпілювати цей код, то отримаємо таку помилку:
$ 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
Ця помилка каже, що ми не можемо явно викликати drop
. Повідомлення використовує загальнопрограмістський термін деструктор, що позначає функцію, яка очищує екземпляр. Деструктор є аналогом до конструктора, який створює екземпляр. Функція drop
в Rust - це деструктор.
Rust не дозволяє нам викликати drop
явно, бо Rust все одно автоматично викличе drop
для цього значення наприкінці main
. Це спричинить помилку подвійного звільнення, бо Rust спробує очистити те саме значення двічі.
Ми не можемо вимкнути автоматичне додавання drop
там, де значення виходить за межі області видимості, і ми не можемо викликати метод drop
явно. Тож якщо нам треба змусити значення очиститися раніше, ми використовуємо функцію std::mem::drop
.
Функція std::mem::drop
відрізняється від методу drop
у трейті Drop
. Ми викликаємо її, передаючи аргументом значення, яке ми хочемо примусово очистити. Ця функція є в прелюдії, таким чином, ми можемо змінити main
у Блоці коду 15-15, щоб викликати функцію drop
, як показано в Блоці коду 15-16:
Файл: 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."); }
Виконання цього коду виведе наступне:
$ 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.
Текст Видалення CustomSmartPointer з даними `деякі дані`!
виводиться між текстами CustomSmartPointer created.
і CustomSmartPointer dropped before the end of main.
, показуючи, що код методу drop
викликається для очищення с
в цьому місці.
Ви можете використати код, вказаний у реалізації трейту Drop
, у різні способи, щоб зробити очищення зручним і безпечним: зокрема, ви могли б використати його для створення власного розподілювача пам'яті! Завдяки трейту Drop
і системі володіння Rust, вам не треба пам'ятати про очищення, бо Rust робить це автоматично.
Також вам не доведеться турбуватися про проблеми, що можуть виникнути через випадкове очищення значень, які все ще використовуються: система власності, яка гарантує, що посилання завжди дійсні, також гарантує, що метод drop
буде викликаний лише один раз, коли значення більше не використовуватиметься.
Тепер, коли ми дослідили Box<T>
і деякі характеристики розумних вказівників, погляньмо на деякі інші розумні вказівники, визначені у стандартній бібліотеці.