Запуск кода при очистке с помощью типажа Drop
Вторым важным типажом умного указателя является Drop, который позволяет регулировать, что происходит, когда значение вот-вот выйдет из области видимости. Вы можете реализовать типаж Drop для любого типа, а также использовать этот код для высвобождения ресурсов, таких как файлы или сетевые соединения.
Мы рассматриваем Drop
в контексте умных указателей, потому что функциональность свойства Drop
практически всегда используется при реализации умного указателя. Например, при сбросе Box<T>
происходит деаллокация пространства на куче, на которое указывает box.
В некоторых языках для некоторых типов программист должен вызывать код для освобождения памяти или ресурсов каждый раз, когда он завершает использование экземпляров этих типов. Примерами могут служить дескрипторы файлов, сокеты или блокировки. Если забыть об этом, система окажется перегруженной и может упасть. В 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` 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
; в типичных случаях вы будете задавать код очистки, который должен выполнить ваш тип, а не печатать сообщение.
Раннее удаление значения с помощью std::mem::drop
К сожалению, отключение функции автоматического удаления с помощью drop
является не простым. Отключение drop
обычно не требуется; весь смысл типажа Drop
в том, чтобы о функции позаботились автоматически. Иногда, однако, вы можете захотеть очистить значение рано. Одним из примеров является использование умных указателей, которые управляют блокировками: вы могли бы потребовать принудительный вызов метода drop
который снимает блокировку, чтобы другой код в той же области видимости мог получить блокировку. Rust не позволяет вызвать метод типажа 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
|
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), который является общим термином программирования для функции, которая очищает экземпляр. Деструктор аналогичен конструктору, который создаёт экземпляр. Функция drop
в Rust является определённым деструктором.
Rust не позволяет обращаться к drop
напрямую, потому что он все равно автоматически вызовет drop
в конце main
. Это вызвало бы ошибку double free, потому что в этом случае 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` 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
, чтобы сделать очистку удобной и безопасной: например, вы можете использовать её для создания своего собственного менеджера памяти! С помощью типажа Drop
и системы владения Rust не нужно специально заботиться о том, чтобы освобождать ресурсы, потому что Rust делает это автоматически.
Также не нужно беспокоиться о проблемах, возникающих в результате случайной очистки значений, которые всё ещё используются: система владения, которая гарантирует, что ссылки всегда действительны, также гарантирует, что drop
вызывается только один раз, когда значение больше не используется.
После того, как мы познакомились с Box<T>
и характеристиками умных указателей, познакомимся с другими умными указателями, определёнными в стандартной библиотеке.