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

Выполнение кода при очистке с помощью трейта 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");
}
Listing 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` 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.

Имя файла: 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");
}
Listing 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
   |
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. Сообщение об ошибке использует термин деструктор, который в программировании в общем смысле означает функцию, очищающую экземпляр. Деструктор аналогичен конструктору, который создает экземпляр. Функция 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");
}
Listing 15-16: Вызов 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> и некоторые характеристики умных указателей, посмотрим на еще несколько умных указателей, определенных в стандартной библиотеке.