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

Запуск коду під час очищення з трейтoм Drop

Другий трейт, важливий для шаблону розумного вказівника, — це Drop, який дає вам змогу налаштувати, що відбувається, коли значення ось-ось вийде з області видимості. Ви можете надати реалізацію для трейту Drop для будь-якого типу, і цей код може використовуватися для звільнення ресурсів, таких як файли або мережеві з’єднання.

Ми вводимо Drop у контексті розумних вказівників, тому що функціональність трейту Drop майже завжди використовується під час реалізації розумного вказівника. Наприклад, коли Box<T> видаляється, він звільняє простір у купі, на який вказує box.

У деяких мовах, для деяких типів, програміст має викликати код для звільнення пам’яті або ресурсів щоразу, коли завершує використання екземпляра цих типів. Приклади включають файлові дескриптори, сокети та блокування. Якщо програміст забуде, система може перевантажитися й аварійно завершити роботу. У Rust ви можете вказати, що певний фрагмент коду має виконуватися щоразу, коли значення виходить з області видимості, і компілятор вставить цей код автоматично. У результаті вам не потрібно бути обережними щодо розміщення коду очищення всюди в програмі, коли роботу з екземпляром певного типу завершено — ви все одно не витечете ресурси!

Ви вказуєте код, який потрібно виконати, коли значення виходить з області видимості, шляхом реалізації трейту Drop. Трейт Drop вимагає, щоб ви реалізували один метод із назвою drop, який приймає змінне посилання на self. Щоб побачити, коли Rust викликає drop, давайте поки що реалізуємо drop із виразами println!.

У списку 15-14 показано структуру CustomSmartPointer, єдина власна функціональність якої полягає в тому, що вона виводитиме Dropping CustomSmartPointer!, коли екземпляр виходитиме з області видимості, щоб показати, коли Rust виконує метод 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");
}

Трейт Drop входить до prelude, тож нам не потрібно вводити його в область видимості. Ми реалізуємо трейт 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` 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 автоматично викликав drop для нас, коли наші екземпляри вийшли з області видимості, викликавши код, який ми вказали. Змінні видаляються у зворотному порядку їх створення, тож d було видалено перед c. Мета цього прикладу — дати вам візуальне уявлення про те, як працює метод drop; зазвичай ви б вказували код очищення, який має виконати ваш тип, а не повідомлення для друку.

На жаль, вимкнути автоматичну функціональність drop не так просто. Вимикати drop зазвичай не потрібно; уся суть трейту Drop у тому, що про це подбано автоматично. Однак інколи вам може знадобитися очистити значення раніше. Один із прикладів — коли використовуються розумні вказівники, які керують блокуваннями: вам може знадобитися примусово викликати метод drop, який звільняє блокування, щоб інший код у тій самій області видимості міг отримати блокування. Rust не дає вам викликати метод drop трейту Drop вручну; натомість вам потрібно викликати функцію std::mem::drop, надану стандартною бібліотекою, якщо ви хочете примусово видалити значення до завершення його області видимості.

Спроба вручну викликати метод drop трейту Drop, змінивши функцію main зі списку 15-14, не спрацює, як показано у списку 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");
}

Коли ми спробуємо скомпілювати цей код, отримаємо таку помилку:

$ 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

Це повідомлення про помилку стверджує, що нам не дозволено явно викликати drop. У повідомленні про помилку використано термін destructor, який є загальним терміном програмування для функції, що очищає екземпляр. Destructor є аналогом constructor, який створює екземпляр. Функція drop у Rust — це один конкретний destructor.

Rust не дає нам явно викликати drop, тому що Rust усе одно автоматично викликав би drop для значення наприкінці main. Це спричинило б помилку подвійного звільнення, оскільки Rust намагався б очистити те саме значення двічі.

Ми не можемо вимкнути автоматичне вставлення drop, коли значення виходить з області видимості, і не можемо викликати метод drop явно. Отже, якщо нам потрібно примусово очистити значення раніше, ми використовуємо функцію std::mem::drop.

Функція std::mem::drop відрізняється від методу drop у трейті Drop. Ми викликаємо її, передаючи як аргумент значення, яке хочемо примусово видалити. Функція є в prelude, тож ми можемо змінити main у списку 15-15, щоб викликати функцію drop, як показано у списку 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");
}

Запуск цього коду виведе таке:

$ 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

Текст Dropping CustomSmartPointer with data `some data`! виводиться між текстом CustomSmartPointer created і CustomSmartPointer dropped before the end of main, показуючи, що код методу drop викликається для видалення c у цей момент.

Ви можете використовувати код, заданий у реалізації трейту Drop, багатьма способами, щоб зробити очищення зручним і безпечним: наприклад, ви могли б використати його, щоб створити власний розподілювач пам’яті! Зі трейтoм Drop і системою власності Rust вам не потрібно пам’ятати про очищення, тому що Rust робить це автоматично.

Вам також не потрібно турбуватися про проблеми, що виникають через випадкове очищення значень, які все ще використовуються: система власності, яка гарантує, що посилання завжди дійсні, також забезпечує, що drop викликається лише один раз, коли значення більше не використовується.

Тепер, коли ми розглянули Box<T> і деякі характеристики розумних вказівників, давайте подивимося на кілька інших розумних вказівників, визначених у стандартній бібліотеці.