When an initialized variable or temporary goes out of scope its destructor is run, or it is dropped. Assignment also runs the destructor of its left-hand operand, if it's initialized. If a variable has been partially initialized, only its initialized fields are dropped.
The destructor of a type T
consists of:
T: Drop
, calling <T as std::ops::Drop>::drop
If a destructor must be run manually, such as when implementing your own smart pointer, std::ptr::drop_in_place
can be used.
Some examples:
#![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; // No destructor run on assignment. moved = PrintOnDrop("Drops when moved"); // Drops now, but is then uninitialized. moved; // Uninitialized does not drop. let uninitialized: PrintOnDrop; // After a partial move, only the remaining fields are dropped. let mut partial_move = (PrintOnDrop("first"), PrintOnDrop("forgotten")); // Perform a partial move, leaving only `partial_move.0` initialized. core::mem::forget(partial_move.1); // When partial_move's scope ends, only the first field is dropped. }
Each variable or temporary is associated to a drop scope. When control flow leaves a drop scope all variables associated to that scope are dropped in reverse order of declaration (for variables) or creation (for temporaries).
Drop scopes are determined after replacing for
, if let
, and while let
expressions with the equivalent expressions using match
. Overloaded operators are not distinguished from built-in operators and binding modes are not considered.
Given a function, or closure, there are drop scopes for:
match
expressionDrop scopes are nested within one another as follows. When multiple scopes are left at once, such as when returning from a function, variables are dropped from the inside outwards.
let
statement is the let
statement's scope.match
guard is the scope of the arm that the guard is for.=>
in a match
expression is the scope of the arm that it's in.match
expression that it belongs to.All function parameters are in the scope of the entire function body, so are dropped last when evaluating the function. Each actual function parameter is dropped after any bindings introduced in that parameter's pattern.
#![allow(unused)] fn main() { struct PrintOnDrop(&'static str); impl Drop for PrintOnDrop { fn drop(&mut self) { println!("drop({})", self.0); } } // Drops the second parameter, then `y`, then the first parameter, then `x` fn patterns_in_parameters( (x, _): (PrintOnDrop, PrintOnDrop), (_, y): (PrintOnDrop, PrintOnDrop), ) {} // drop order is 3 2 0 1 patterns_in_parameters( (PrintOnDrop("0"), PrintOnDrop("1")), (PrintOnDrop("2"), PrintOnDrop("3")), ); }
Local variables declared in a let
statement are associated to the scope of the block that contains the let
statement. Local variables declared in a match
expression are associated to the arm scope of the match
arm that they are declared in.
#![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"); }
If multiple patterns are used in the same arm for a match
expression, then an unspecified pattern will be used to determine the drop order.
The temporary scope of an expression is the scope that is used for the temporary variable that holds the result of that expression when used in a place context, unless it is promoted.
Apart from lifetime extension, the temporary scope of an expression is the smallest scope that contains the expression and is for one of the following:
if
, while
or loop
expression.else
block of an if
expression.if
or while
expression, or a match
guard.Notes:
Temporaries that are created in the final expression of a function body are dropped after any named variables bound in the function body, as there is no smaller enclosing temporary scope.
The scrutinee of a
match
expression is not a temporary scope, so temporaries in the scrutinee can be dropped after thematch
expression. For example, the temporary for1
inmatch 1 { ref mut z => z };
lives until the end of the statement.
Some examples:
#![allow(unused)] fn main() { struct PrintOnDrop(&'static str); impl Drop for PrintOnDrop { fn drop(&mut self) { println!("drop({})", self.0); } } let local_var = PrintOnDrop("local var"); // Dropped once the condition has been evaluated if PrintOnDrop("If condition").0 == "If condition" { // Dropped at the end of the block PrintOnDrop("If body").0 } else { unreachable!() }; // Dropped at the end of the statement (PrintOnDrop("first operand").0 == "" // Dropped at the ) || PrintOnDrop("second operand").0 == "") // Dropped at the end of the expression || PrintOnDrop("third operand").0 == ""; // Dropped at the end of the function, after local variables. // Changing this to a statement containing a return expression would make the // temporary be dropped before the local variables. Binding to a variable // which is then returned would also make the temporary be dropped first. match PrintOnDrop("Matched value in final expression") { // Dropped once the condition has been evaluated _ if PrintOnDrop("guard condition").0 == "" => (), _ => (), } }
Temporaries are also created to hold the result of operands to an expression while the other operands are evaluated. The temporaries are associated to the scope of the expression with that operand. Since the temporaries are moved from once the expression is evaluated, dropping them has no effect unless one of the operands to an expression breaks out of the expression, returns, or panics.
#![allow(unused)] fn main() { struct PrintOnDrop(&'static str); impl Drop for PrintOnDrop { fn drop(&mut self) { println!("drop({})", self.0); } } loop { // Tuple expression doesn't finish evaluating so operands drop in reverse order ( PrintOnDrop("Outer tuple first"), PrintOnDrop("Outer tuple second"), ( PrintOnDrop("Inner tuple first"), PrintOnDrop("Inner tuple second"), break, ), PrintOnDrop("Never created"), ); } }
Promotion of a value expression to a 'static
slot occurs when the expression could be written in a constant, borrowed, and dereferencing that borrow where the expression was originally written, without changing the runtime behavior. That is, the promoted expression can be evaluated at compile-time and the resulting value does not contain interior mutability or destructors (these properties are determined based on the value where possible, e.g. &None
always has the type &'static Option<_>
, as it contains nothing disallowed).
Note: The exact rules for temporary lifetime extension are subject to change. This is describing the current behavior only.
The temporary scopes for expressions in let
statements are sometimes extended to the scope of the block containing the let
statement. This is done when the usual temporary scope would be too small, based on certain syntactic rules. For example:
#![allow(unused)] fn main() { let x = &mut 0; // Usually a temporary would be dropped by now, but the temporary for `0` lives // to the end of the block. println!("{}", x); }
If a borrow, dereference, field, or tuple indexing expression has an extended temporary scope then so does its operand. If an indexing expression has an extended temporary scope then the indexed expression also has an extended temporary scope.
An extending pattern is either
So ref x
, V(ref x)
and [ref x, y]
are all extending patterns, but x
, &ref x
and &(ref x,)
are not.
If the pattern in a let
statement is an extending pattern then the temporary scope of the initializer expression is extended.
For a let statement with an initializer, an extending expression is an expression which is one of the following:
So the borrow expressions in &mut 0
, (&1, &mut 2)
, and Some { 0: &mut 3 }
are all extending expressions. The borrows in &0 + &1
and Some(&mut 0)
are not: the latter is syntactically a function call expression.
The operand of any extending borrow expression has its temporary scope extended.
Here are some examples where expressions have extended temporary scopes:
#![allow(unused)] fn main() { fn temp() {} trait Use { fn use_temp(&self) -> &Self { self } } impl Use for () {} // The temporary that stores the result of `temp()` lives in the same scope // as x in these cases. let x = &temp(); let x = &temp() as &dyn Send; let x = (&*&temp(),); let x = { [Some { 0: &temp(), }] }; let ref x = temp(); let ref x = *&temp(); x; }
Here are some examples where expressions don't have extended temporary scopes:
#![allow(unused)] fn main() { fn temp() {} trait Use { fn use_temp(&self) -> &Self { self } } impl Use for () {} // The temporary that stores the result of `temp()` only lives until the // end of the let statement in these cases. let x = Some(&temp()); // ERROR let x = (&temp()).use_temp(); // ERROR x; }
Not running destructors in Rust is safe even if it has a type that isn't 'static
. std::mem::ManuallyDrop
provides a wrapper to prevent a variable or field from being dropped automatically.
© 2010 The Rust Project Developers
Licensed under the Apache License, Version 2.0 or the MIT license, at your option.
https://doc.rust-lang.org/reference/destructors.html