Выполнение кода при очистке с помощью типажа Drop
Вторым важным типажом для паттерна умного указателя является Drop, который позволяет настроить, что происходит, когда значение выходит из области видимости. Вы можете предоставить реализацию типажа Drop для любого типа, и этот код может использоваться для освобождения ресурсов, таких как файлы или сетевые соединения.
Мы представляем Drop в контексте умных указателей, потому что функциональность типажа Drop почти всегда используется при реализации умного указателя. Например, когда Box<T> удаляется, он освобождает пространство в куче, на которое указывает бокс.
В некоторых языках программист должен вручную вызывать код для освобождения памяти или ресурсов каждый раз, когда заканчивает использование экземпляра этих типов. Примеры включают дескрипторы файлов, сокеты и мьютексы. Если забыть, система может перегрузиться и упасть. В 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."); }
CustomSmartPointer, реализующая типаж Drop, в которую мы бы поместили наш код очисткиТипаж 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; обычно вы бы указали код очистки, который требуется вашему типу, а не сообщение для печати.
К сожалению, отключить автоматическую функциональность drop непросто. Отключение drop обычно не требуется; вся суть типажа Drop в том, что он обрабатывается автоматически. Однако иногда вы можете захотеть очистить значение раньше. One example is when using smart pointers that manage locks: you might want to force the drop method that releases the lock so that other code in the same scope can acquire the lock. 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.");
}
drop из типажа Drop для ранней очисткиПри попытке скомпилировать этот код мы получим следующую ошибку:
$ 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. Сообщение об ошибке использует термин деструктор, который является общим термином программирования для функции, очищающей экземпляр. Деструктор аналогичен конструктору, который создаёт экземпляр. Функция drop в Rust — это один particular destructor.
Rust не позволяет нам вызывать drop явно, потому что Rust всё равно автоматически вызовет drop для значения в конце main. Это вызвало бы ошибку двойного освобождения, потому что Rust попытался бы очистить одно и то же значение дважды.
Мы не можем отключить автоматическую вставку drop при выходе значения из области видимости и не можем вызвать метод drop явно. Поэтому, если нам нужно принудительно очистить значение раньше, мы используем функцию std::mem::drop.
Функция std::mem::drop отличается от метода drop в типаже Drop. Мы вызываем её, передавая в качестве аргумента значение, которое хотим принудительно удалить. Функция находится в прелюдии, поэтому мы можем изменить 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."); }
std::mem::drop для явного удаления значения до выхода из области видимостиЗапуск этого кода выведет следующее:
$ 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> и некоторые характеристики умных указателей, давайте посмотрим на несколько других умных указателей, определённых в стандартной библиотеке.