Деструкторы
Когда инициализированная переменная или временное значение выходит из области видимости, её деструктор запускается, или она сбрасывается. Присваивание также запускает деструктор своего левого операнда, если он инициализирован. Если переменная была частично инициализирована, сбрасываются только её инициализированные поля.
Деструктор типа T состоит из:
- Если
T: Drop, вызов<T as core::ops::Drop>::drop - Рекурсивный запуск деструктора всех его полей.
- Поля структуры сбрасываются в порядке объявления.
- Поля активного варианта перечисления сбрасываются в порядке объявления.
- Поля кортежа сбрасываются по порядку.
- Элементы массива или владеющего среза сбрасываются от первого элемента к последнему.
- Переменные, захваченные замыканием по перемещению, сбрасываются в неопределённом порядке.
- Объекты трейтов запускают деструктор базового типа.
- Другие типы не приводят к каким-либо дополнительным сбросам.
Если деструктор должен быть запущен вручную, например, при реализации собственного умного
указателя, можно использовать core::ptr::drop_in_place.
Некоторые примеры:
#![allow(unused)] fn main() { struct PrintOnDrop(&'static str); impl Drop for PrintOnDrop { fn drop(&mut self) { println!("{}", self.0); } } let mut overwritten = PrintOnDrop("drops when overwritten"); overwritten = PrintOnDrop("drops when scope ends"); let tuple = (PrintOnDrop("Tuple first"), PrintOnDrop("Tuple second")); let moved; // Деструктор не запускается при присваивании. moved = PrintOnDrop("Drops when moved"); // Сбрасывается сейчас, но затем становится неинициализированным. moved; // Неинициализированная переменная не сбрасывается. let uninitialized: PrintOnDrop; // После частичного перемещения сбрасываются только оставшиеся поля. let mut partial_move = (PrintOnDrop("first"), PrintOnDrop("forgotten")); // Выполняется частичное перемещение, оставляя инициализированным только `partial_move.0`. core::mem::forget(partial_move.1); // Когда область видимости partial_move заканчивается, сбрасывается только первое поле. }
Области сброса
Каждая переменная или временное значение связаны с областью сброса. Когда поток управления покидает область сброса, все переменные, связанные с этой областью, сбрасываются в обратном порядке объявления (для переменных) или создания (для временных значений).
Области сброса могут быть определены путём замены выражений for, if и while
эквивалентными выражениями с использованием match, loop и
break.
Перегруженные операторы не отличаются от встроенных операторов, и режимы связывания не учитываются.
Для функции или замыкания существуют области сброса для:
- Всей функции
- Каждого оператора
- Каждого выражения
- Каждого блока, включая тело функции
- В случае блочного выражения, область видимости для блока и выражения совпадают.
- Каждой ветки выражения
match
Области сброса вложены друг в друга следующим образом. Когда несколько областей покидаются одновременно, например, при возврате из функции, переменные сбрасываются изнутри наружу.
- Область видимости всей функции является самой внешней областью.
- Блок тела функции содержится внутри области видимости всей функции.
- Родителем выражения в операторе-выражении является область видимости оператора.
- Родителем инициализатора оператора
letявляется область видимости оператораlet.
- Родителем области видимости оператора является область видимости блока, содержащего оператор.
- Родителем выражения для ограничителя
matchявляется область видимости ветки, для которой предназначен ограничитель.
- Родителем выражения после
=>в выраженииmatchявляется область видимости ветки, в которой оно находится.
- Родителем области видимости ветки является область видимости выражения
match, к которому она принадлежит.
- Родителем всех других областей видимости является область видимости непосредственно охватывающего выражения.
Области видимости параметров функции
Все параметры функции находятся в области видимости всего тела функции, поэтому сбрасываются последними при оценке функции. Каждый фактический параметр функции сбрасывается после любых привязок, введённых в шаблоне этого параметра.
#![allow(unused)] fn main() { struct PrintOnDrop(&'static str); impl Drop for PrintOnDrop { fn drop(&mut self) { println!("drop({})", self.0); } } // Сбрасывает `y`, затем второй параметр, затем `x`, затем первый параметр fn patterns_in_parameters( (x, _): (PrintOnDrop, PrintOnDrop), (_, y): (PrintOnDrop, PrintOnDrop), ) {} // порядок сброса: 3 2 0 1 patterns_in_parameters( (PrintOnDrop("0"), PrintOnDrop("1")), (PrintOnDrop("2"), PrintOnDrop("3")), ); }
Области видимости локальных переменных
Локальные переменные, объявленные в операторе let, связаны с областью видимости
блока, содержащего оператор let. Локальные переменные, объявленные в
выражении match, связаны с областью видимости ветки match, в которой они
объявлены.
#![allow(unused)] fn main() { struct PrintOnDrop(&'static str); impl Drop for PrintOnDrop { fn drop(&mut self) { println!("drop({})", self.0); } } let declared_first = PrintOnDrop("Dropped last in outer scope"); { let declared_in_block = PrintOnDrop("Dropped in inner scope"); } let declared_last = PrintOnDrop("Dropped first in outer scope"); }
Переменные в шаблонах сбрасываются в обратном порядке объявления внутри шаблона.
#![allow(unused)] fn main() { struct PrintOnDrop(&'static str); impl Drop for PrintOnDrop { fn drop(&mut self) { println!("drop({})", self.0); } } let (declared_first, declared_last) = ( PrintOnDrop("Dropped last"), PrintOnDrop("Dropped first"), ); }
Для целей порядка сброса, or-шаблоны объявляют привязки в порядке, заданном первым подшаблоном.
#![allow(unused)] fn main() { struct PrintOnDrop(&'static str); impl Drop for PrintOnDrop { fn drop(&mut self) { println!("drop({})", self.0); } } // Сбрасывает `x` перед `y`. fn or_pattern_drop_order<T>( (Ok([x, y]) | Err([y, x])): Result<[T; 2], [T; 2]> // ^^^^^^^^^^ ^^^^^^^^^^^ Это второй подшаблон. // | // Это первый подшаблон. // // В первом подшаблоне `x` объявлен перед `y`. Поскольку это // первый подшаблон, этот порядок используется, даже если сопоставляется // второй подшаблон, где привязки объявлены в противоположном // порядке. ) {} // Здесь мы сопоставляем первый подшаблон, и сбросы происходят согласно // порядку объявления в первом подшаблоне. or_pattern_drop_order(Ok([ PrintOnDrop("Declared first, dropped last"), PrintOnDrop("Declared last, dropped first"), ])); // Здесь мы сопоставляем второй подшаблон, и сбросы всё ещё происходят // согласно порядку объявления в первом подшаблоне. or_pattern_drop_order(Err([ PrintOnDrop("Declared last, dropped first"), PrintOnDrop("Declared first, dropped last"), ])); }
Области видимости временных значений
Область видимости временного значения выражения — это область видимости, которая используется для временной переменной, содержащей результат этого выражения, когда оно используется в контексте места, если оно не продвинуто.
Помимо расширения времени жизни, область видимости временного значения выражения — это наименьшая область видимости, содержащая выражение и являющаяся одной из следующих:
- Вся функция.
- Оператор.
- Тело выражения
if,whileилиloop. - Блок
elseвыраженияif. - Условие без сопоставления с образцом выражения
ifилиwhile, или ограничительmatch. - Выражение тела для ветки match.
- Каждый операнд ленивого булева выражения.
- Условие(я) сопоставления с образцом и последующее тело
if(destructors.scope.temporary.edition2024). - Условие сопоставления с образцом и тело цикла
while. - Все завершающее выражение блока (destructors.scope.temporary.edition2024).
Note
Скретини выражения
matchне является областью видимости временного значения, поэтому временные значения в скрутини могут быть сброшены после выраженияmatch. Например, временное значение для1вmatch 1 { ref mut z => z };живёт до конца оператора.
Note
Десугаризация деструктурирующего присваивания ограничивает область видимости временного значения своего операнда присваиваемого значения (правой части). Подробности см. в expr.assign.destructure.tmp-scopes.
2024 Edition differences
В редакции 2024 года были добавлены два новых правила сужения области видимости временных значений: временные значения
if letсбрасываются перед блокомelse, и временные значения завершающих выражений блоков сбрасываются сразу после оценки завершающего выражения.
Некоторые примеры:
#![allow(unused)] fn main() { #![allow(irrefutable_let_patterns)] struct PrintOnDrop(&'static str); impl Drop for PrintOnDrop { fn drop(&mut self) { println!("drop({})", self.0); } } let local_var = PrintOnDrop("local var"); // Сбрасывается после оценки условия if PrintOnDrop("If condition").0 == "If condition" { // Сбрасывается в конце блока PrintOnDrop("If body").0 } else { unreachable!() }; if let "if let scrutinee" = PrintOnDrop("if let scrutinee").0 { PrintOnDrop("if let consequent").0 // `if let consequent` сбрасывается здесь } // `if let scrutinee` сбрасывается здесь else { PrintOnDrop("if let else").0 // `if let else` сбрасывается здесь }; while let x = PrintOnDrop("while let scrutinee").0 { PrintOnDrop("while let loop body").0; break; // `while let loop body` сбрасывается здесь. // `while let scrutinee` сбрасывается здесь. } // Сбрасывается перед первым || (PrintOnDrop("first operand").0 == "" // Сбрасывается перед ) || PrintOnDrop("second operand").0 == "") // Сбрасывается перед ; || PrintOnDrop("third operand").0 == ""; // Скретини сбрасывается в конце функции, перед локальными переменными // (поскольку это завершающее выражение блока тела функции). match PrintOnDrop("Matched value in final expression") { // Сбрасывается после оценки условия _ if PrintOnDrop("guard condition").0 == "" => (), _ => (), } }
Операнды
Временные значения также создаются для хранения результата операндов выражения пока оцениваются другие операнды. Временные значения связаны с областью видимости выражения с этим операндом. Поскольку временные значения перемещаются из после оценки выражения, их сброс не имеет эффекта, если один из операндов выражения не выходит из выражения, не возвращается или не паникует.
#![allow(unused)] fn main() { struct PrintOnDrop(&'static str); impl Drop for PrintOnDrop { fn drop(&mut self) { println!("drop({})", self.0); } } loop { // Выражение кортежа не завершает оценку, поэтому операнды сбрасываются в обратном порядке ( PrintOnDrop("Outer tuple first"), PrintOnDrop("Outer tuple second"), ( PrintOnDrop("Inner tuple first"), PrintOnDrop("Inner tuple second"), break, ), PrintOnDrop("Never created"), ); } }
Продвижение констант
Продвижение выражения значения в слот 'static происходит, когда выражение
может быть записано в константу и заимствовано, и это заимствование может быть разыменовано там,
где выражение было изначально записано, без изменения поведения во время выполнения.
То есть, продвинутое выражение может быть вычислено во время компиляции, и
результирующее значение не содержит внутренней изменяемости или деструкторов (эти
свойства определяются на основе значения, где это возможно, например, &None
всегда имеет тип &'static Option<_>, так как он не содержит ничего запрещённого).
Расширение времени жизни временных значений
Note
Точные правила расширения времени жизни временных значений могут быть изменены. Здесь описывается только текущее поведение.
Области видимости временных значений для выражений в операторах let иногда
расширяются до области видимости блока, содержащего оператор let. Это
делается, когда обычная область видимости временного значения была бы слишком мала, на основе определённых
синтаксических правил. Например:
#![allow(unused)] fn main() { let x = &mut 0; // Обычно временное значение было бы сброшено к этому моменту, но временное значение для `0` живёт // до конца блока. println!("{}", x); }
Расширение времени жизни также применяется к элементам static и const, где оно
заставляет временные значения жить до конца программы. Например:
#![allow(unused)] fn main() { const C: &Vec<i32> = &Vec::new(); // Обычно это была бы висячая ссылка, так как `Vec` будет // существовать только внутри инициализатора `C`, но вместо этого // заимствование получает расширенное время жизни, так что оно эффективно имеет время жизни `'static`. println!("{:?}", C); }
Если заимствование, разыменование, поле или индексация кортежа имеют расширенную область видимости временного значения, то его операнд также имеет её. Если индексация имеет расширенную область видимости временного значения, то индексируемое выражение также имеет расширенную область видимости временного значения.
Расширение на основе шаблонов
Расширяющий шаблон — это либо:
-
Идентификаторный шаблон, который связывает по ссылке или изменяемой ссылке.
#![allow(unused)] fn main() { fn temp() {} let ref x = temp(); // Связывает по ссылке. x; let ref mut x = temp(); // Связывает по изменяемой ссылке. x; } -
Структурный, кортежный, кортежно-структурный, срезовый или or-шаблон, где по крайней мере один из прямых подшаблонов является расширяющим шаблоном.
#![allow(unused)] fn main() { use core::sync::atomic::{AtomicU64, Ordering::Relaxed}; static X: AtomicU64 = AtomicU64::new(0); struct W<T>(T); impl<T> Drop for W<T> { fn drop(&mut self) { X.fetch_add(1, Relaxed); } } let W { 0: ref x } = W(()); // Структурный шаблон. x; let W(ref x) = W(()); // Кортежно-структурный шаблон. x; let (W(ref x),) = (W(()),); // Кортежный шаблон. x; let [W(ref x), ..] = [W(())]; // Срезовый шаблон. x; let (Ok(W(ref x)) | Err(&ref x)) = Ok(W(())); // Or-шаблон. x; // // Все временные значения выше всё ещё живы здесь. assert_eq!(0, X.load(Relaxed)); }
Таким образом, ref x, V(ref x) и [ref x, y] — все расширяющие шаблоны, но x, &ref x и &(ref x,) — нет.
Если шаблон в операторе let является расширяющим шаблоном, то область видимости временного значения
инициализатора расширяется.
#![allow(unused)] fn main() { fn temp() {} // Это расширяющий шаблон, поэтому область видимости временного значения расширена. let ref x = *&temp(); // OK x; }
#![allow(unused)] fn main() { fn temp() {} // Это ни расширяющий шаблон, ни расширяющее выражение, // поэтому временное значение сбрасывается в точке с запятой. let &ref x = *&&temp(); // ERROR x; }
#![allow(unused)] fn main() { fn temp() {} // Это не расширяющий шаблон, но это расширяющее выражение, // поэтому временное значение живёт за пределами оператора `let`. let &ref x = &*&temp(); // OK x; }
Расширение на основе выражений
Для оператора let с инициализатором, расширяющее выражение — это выражение, которое является одним из следующих:
- Выражение инициализатора.
- Операнд расширяющего выражения заимствования.
- Супер-операнды расширяющего вызова супер-макроса.
- Операнд(ы) расширяющего выражения массива, приведения, блочной структуры или кортежа.
- Аргументы расширяющего конструктора кортежной структуры или кортежного варианта перечисления.
- Завершающее выражение расширяющего блочного выражения, за исключением асинхронного блочного выражения.
- Завершающее выражение расширяющего блока следствия,
else ifилиelseвыраженияif. - Выражение ветки расширяющего выражения
match.
Note
Десугаризация деструктурирующего присваивания делает его операнд присваиваемого значения (правую часть) расширяющим выражением внутри вновь введённого блока. Подробности см. в expr.assign.destructure.tmp-ext.
Таким образом, выражения заимствования в &mut 0, (&1, &mut 2) и Some(&mut 3)
— все расширяющие выражения. Заимствования в &0 + &1 и f(&mut 0) — нет.
Операнд расширяющего выражения заимствования имеет свою область видимости временного значения расширенной.
Супер-временные значения расширяющего вызова супер-макроса имеют свои области видимости расширенными.
Note
rustcне рассматривает операнды повторения массива расширяющих выражений массива как расширяющие выражения. Должен ли он — открытый вопрос.Подробности см. в Rust issue #146092.
Примеры
Вот некоторые примеры, где выражения имеют расширенные области видимости временных значений:
#![allow(unused)] fn main() { use core::pin::pin; use core::sync::atomic::{AtomicU64, Ordering::Relaxed}; static X: AtomicU64 = AtomicU64::new(0); #[derive(Debug)] struct S; impl Drop for S { fn drop(&mut self) { X.fetch_add(1, Relaxed); } } const fn temp() -> S { S } let x = &temp(); // Операнд заимствования. x; let x = &raw const *&temp(); // Операнд сырого заимствования. assert_eq!(X.load(Relaxed), 0); let x = &temp() as &dyn Send; // Операнд приведения. x; let x = (&*&temp(),); // Операнд конструктора кортежа. x; struct W<T>(T); let x = W(&temp()); // Аргумент конструктора кортежной структуры. x; let x = Some(&temp()); // Аргумент конструктора кортежного варианта перечисления. x; let x = { [Some(&temp())] }; // Завершающее выражение блока. x; let x = const { &temp() }; // Завершающее выражение блока `const`. x; let x = unsafe { &temp() }; // Завершающее выражение блока `unsafe`. x; let x = if true { &temp() } else { &temp() }; // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // Завершающие выражения блоков `if`/`else`. x; let x = match () { _ => &temp() }; // Выражение ветки `match`. x; let x = pin!(temp()); // Супер-операнд выражения вызова супер-макроса. x; let x = pin!({ &mut temp() }); // Как выше. x; let x = format_args!("{:?}", temp()); // Как выше. x; // // Все временные значения выше всё ещё живы здесь. assert_eq!(0, X.load(Relaxed)); }
Вот некоторые примеры, где выражения не имеют расширенных областей видимости временных значений:
#![allow(unused)] fn main() { fn temp() {} // Аргументы вызовов функций не являются расширяющими выражениями. // Временное значение сбрасывается в точке с запятой. let x = core::convert::identity(&temp()); // ERROR x; }
#![allow(unused)] fn main() { fn temp() {} trait Use { fn use_temp(&self) -> &Self { self } } impl Use for () {} // Получатели вызовов методов не являются расширяющими выражениями. let x = (&temp()).use_temp(); // ERROR x; }
#![allow(unused)] fn main() { fn temp() {} // Скретини выражений match не являются расширяющими выражениями. let x = match &temp() { x => x }; // ERROR x; }
#![allow(unused)] fn main() { fn temp() {} // Завершающие выражения `async` блоков не являются расширяющими выражениями. let x = async { &temp() }; // ERROR x; }
#![allow(unused)] fn main() { fn temp() {} // Завершающие выражения замыканий не являются расширяющими выражениями. let x = || &temp(); // ERROR x; }
#![allow(unused)] fn main() { fn temp() {} // Операнды break в циклах не являются расширяющими выражениями. let x = loop { break &temp() }; // ERROR x; }
#![allow(unused)] fn main() { fn temp() {} // Операнды break к меткам не являются расширяющими выражениями. let x = 'a: { break 'a &temp() }; // ERROR x; }
#![allow(unused)] fn main() { use core::pin::pin; fn temp() {} // Аргумент `pin!` является расширяющим выражением только если вызов // является расширяющим выражением. Поскольку это не так, внутренний блок не // является расширяющим выражением, поэтому временные значения в его завершающем // выражении сбрасываются немедленно. pin!({ &temp() }); // ERROR }
#![allow(unused)] fn main() { fn temp() {} // Как выше. format_args!("{:?}", { &temp() }); // ERROR }
Невыполнение деструкторов
Подавление деструкторов вручную
core::mem::forget может быть использован для предотвращения запуска деструктора переменной,
и core::mem::ManuallyDrop предоставляет обёртку для предотвращения
автоматического сброса переменной или поля.
Note
Предотвращение запуска деструктора с помощью
core::mem::forgetили другими средствами безопасно, даже если тип не является'static. Помимо мест, где деструкторы гарантированно выполняются, как определено в этом документе, типы не могут безопасно полагаться на выполнение деструктора для корректности.
Завершение процесса без раскрутки стека
Существуют способы завершить процесс без раскрутки стека, и в этом случае деструкторы не будут запущены.
Стандартная библиотека предоставляет std::process::exit и std::process::abort для явного выполнения этого. Кроме того, если обработчик паники установлен в abort, паника всегда будет завершать процесс без запуска деструкторов.
Есть один дополнительный случай, о котором следует знать: когда паника достигает границы ABI без раскрутки, либо не будет запущен ни один деструктор, либо будут запущены все деструкторы до границы ABI.