Запуск кода при очистке с помощью типажа Drop

Второй типаж, важный для шаблона умного указателя, - это Drop, который позволяет настроить то, что происходит, когда значение собирается выйти из области видимости. Вы можете предоставить реализацию Drop для любого типа, а указанный код можно использовать для освобождения ресурсов, таких как файлы или сетевые подключения. Мы представим Drop в контексте умных указателей, потому что функциональность типажа Drop почти всегда используется при их реализации. Например, Box<T> настраивает Drop для освобождения пространства в куче, на которое указывает 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.");
}

Листинг 15-14: Структура 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 [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.");
}

Листинг 15-15: Попытка вызвать метод 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: `drop(c)`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0040`.
error: could not compile `drop-example`

To learn more, run the command again with --verbose.

Это сообщение об ошибке говорит, что мы не можем явно вызывать drop. В сообщении об ошибке используется термин деструктор (destructor), который является общим термином программирования для функции, которая очищает экземпляр. Деструктор аналогичен конструктору, который создаёт экземпляр. Функция 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.");
}

Листинг 15-16: Вызов std::mem::drop для явного удаления значения до его выхода из области видимости

Выполнение данного кода выведет следующий результат::

$ 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.

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

Вы можете использовать код, указанный в реализации типажа Drop, чтобы сделать очистку удобной и безопасной: например, вы можете использовать её для создания своего собственного менеджера памяти! С помощью типажа Drop и системы владения Rust не нужно специально заботиться о том, чтобы освобождать ресурсы, потому что Rust делает это автоматически.

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

После того, как мы познакомились с Box<T> и характеристиками умных указателей, познакомимся с её другими умными указателями, определёнными в стандартной библиотеке.