Rust manages memory without a garbage collector. Every value has one owner, the value is destroyed when its owner goes out of scope, and references to it are checked at compile time. The compiler’s borrow checker enforces this. The Reference covers the parts on this page in “Expressions”, “Destructors”, “Lifetime elision” and “Subtyping and variance”; it does not give a full specification of the borrow checker, so the rules for references below are stated in general terms.
Ownership
- Each value has one owner: a variable, a field, an element or a temporary.
- When the owner goes out of scope the value is dropped.
- Assigning a value or passing it to a function moves it, unless its type
is
Copy. After a move the old owner cannot be used.
let a = String::from("hello");
let b = a; // the String is moved to b
// println!("{a}"); // error: borrow of moved value
println!("{b}");
fn consume(s: String) {}
consume(b); // moved into the parameter; dropped when the function ends
Moves and copies
A place expression denotes a memory location: a variable, a static, a
dereference *expr, an index expr[expr] or a field
expr.f. When a place is used where a value is needed, or bound by value in
a pattern, the value is copied if its type is Copy and moved otherwise.
Only these can be moved out of (Reference: Expressions):
- Variables that are not currently borrowed
- Temporary values
- Fields of a place that can be moved out of and that does not implement
Drop - The result of dereferencing a
Box<T>that can be moved out of
After a move out of a local variable, the variable is uninitialised and can only be used again by assigning to it. Moving out of one field leaves a partial move: the other fields stay usable, but the whole value does not.
struct Person { name: String, age: u32 }
let p = Person { name: String::from("Ada"), age: 36 };
let n = p.name; // partial move
println!("{}", p.age); // ok: age was not moved
// let q = p; // error: p was partly moved
let mut s = String::from("x");
drop(s); // moved
s = String::from("y"); // re-initialised; s is usable again
The Copy trait
A type that implements Copy is duplicated instead of moved. It can be
implemented only for types that do not implement Drop and whose fields
are all Copy (Reference: Special types and traits). The usual way is
#[derive(Copy, Clone)].
Copy | Not Copy |
|---|---|
Integers, floats, bool, char | String, Vec<T>, Box<T> |
&T, raw pointers, function pointers, function items | &mut T |
Tuples and arrays of Copy values | Tuples and arrays with a non-Copy element |
Closures capturing nothing or only Copy values | Closures that capture by mutable reference, or by value a non-Copy value |
Clone is an explicit, possibly expensive duplication, called with
.clone(). It is a supertrait of Copy.
References
A reference borrows a value without taking ownership. &expr gives a
shared reference and &mut expr a mutable, exclusive reference (see
Borrow operators).
| Rule | Effect |
|---|---|
| At any point, either many shared references or one mutable reference | A &mut reference excludes all other access to the value while it is in use |
| A reference must not outlive the value it points to | The compiler rejects dangling references |
| A borrow lasts until its last use, not to the end of the scope | Known as non-lexical lifetimes |
| While a value is borrowed it cannot be moved | Moving out of a borrowed variable is rejected |
let mut v = vec![1, 2, 3];
let first = &v[0];
println!("{first}"); // last use of first; the shared borrow ends here
v.push(4); // ok: a mutable borrow
let r = &mut v;
r.push(5);
let r2 = &mut *r; // a reborrow; r is unusable while r2 is live
r2.push(6);
r.push(7); // ok: r2 is finished
Mutation behind a shared reference is possible only through interior
mutability: Cell, RefCell, Mutex and the
atomics, all built on UnsafeCell<T>. A static whose type has interior
mutability is not placed in read-only memory.
Lifetimes
A lifetime names a span during which a reference is valid. Lifetimes are written as a quote and an identifier, and they are declared as generic parameters. They do not change what a program does; they let the compiler check that it is correct.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
struct Excerpt<'a> { text: &'a str } // the struct cannot outlive text
impl<'a> Excerpt<'a> {
fn get(&self) -> &'a str { self.text }
}
| Form | Meaning |
|---|---|
'static | Lives for the whole program. String literals are &'static str |
'_ | Placeholder: asks for an inferred lifetime; preferred when eliding a lifetime in a path |
'a: 'b | 'a outlives 'b |
T: 'a | Every lifetime in T outlives 'a |
for<'a> | Higher-ranked: for every lifetime 'a |
T: 'static means T holds no non-static references. It does
not mean the value lives forever.
Lifetime elision
Lifetimes may be left out of function item, function pointer and closure trait signatures, following these rules (Reference: Lifetime elision). It is an error to leave one out when these rules do not decide it.
- Each elided lifetime in the parameters becomes a separate lifetime parameter.
- If there is exactly one lifetime used in the parameters, elided or not, it is assigned to all elided output lifetimes.
- In a method, if the receiver has type
&Selfor&mut Self, the lifetime of that reference is assigned to all elided output lifetimes.
| Elided | Expanded |
|---|---|
fn print(s: &str); | fn print<'a>(s: &'a str); |
fn sub(s: &str, n: usize) -> &str; | fn sub<'a>(s: &'a str, n: usize) -> &'a str; |
fn get_mut(&mut self) -> &mut T; | fn get_mut<'a>(&'a mut self) -> &'a mut T; |
fn new(buf: &mut [u8]) -> Thing<'_>; | fn new<'a>(buf: &'a mut [u8]) -> Thing<'a>; |
fn get() -> &str; | Error: no parameter to take a lifetime from |
fn f(s: &str, t: &str) -> &str; | Error: two candidates |
Default object lifetimes
When a trait object has no
lifetime bound, a default applies. If it is a type argument of a generic type, the
containing type is tried first: a unique bound there is the default, and several
bounds mean one must be written. Otherwise the bounds on the trait are used: a single
lifetime bound is the default; 'static if any bound is
'static; and if the trait has no lifetime bounds the lifetime is inferred
in expressions and is 'static outside them. Writing '_ uses
the normal elision rules instead.
| Type | Same as |
|---|---|
Box<dyn Foo> | Box<dyn Foo + 'static> |
&'a dyn Foo | &'a (dyn Foo + 'a) |
&'a Box<dyn Foo> | &'a Box<dyn Foo + 'static> |
Constants and statics
A reference type in a const or static item is implicitly
'static unless a lifetime is written. If the item contains function or
closure references with their own lifetimes, the compiler tries the standard
elision rules on those first.
Subtyping and variance
Subtyping applies only to lifetimes and to types with higher-ranked lifetimes. Because
'static outlives 'a, &'static str is a subtype
of &'a str and may be used wherever one is expected. Variance
says how a type’s subtyping follows that of its parameters (Reference: Subtyping and
variance).
| Type | Variance in 'a | Variance in T |
|---|---|---|
&'a T | Covariant | Covariant |
&'a mut T | Covariant | Invariant |
*const T | Covariant | |
*mut T | Invariant | |
[T], [T; n] | Covariant | |
fn() -> T | Covariant | |
fn(T) | Contravariant | |
UnsafeCell<T> | Invariant | |
PhantomData<T> | Covariant | |
dyn Trait<T> + 'a | Covariant | Invariant |
Destructors and drop order
When an initialised variable or temporary goes out of scope, its destructor runs. An
assignment also runs the destructor of the old value. A destructor first calls
Drop::drop if the type implements Drop, and then runs the
destructors of the fields (Reference: Destructors).
| Kind | Order |
|---|---|
| Struct fields | In declaration order |
| Enum fields | The active variant’s fields, in declaration order |
| Tuple and array elements | First to last |
| Closure captures | Variables captured by move, in an unspecified order |
| Trait objects | The underlying type’s destructor |
| Local variables | Reverse order of declaration |
| Bindings in one pattern | Reverse order of declaration: in let (a, b) = …, b is dropped first |
| Function parameters | After the body’s locals. Each parameter is dropped after the bindings of its own pattern |
Drop::drop cannot be called directly. std::mem::drop(x) moves
a value into a function that lets it go out of scope. To run a destructor by hand, as
in a smart pointer, use std::ptr::drop_in_place.
struct Noisy(&'static str);
impl Drop for Noisy {
fn drop(&mut self) { println!("drop {}", self.0); }
}
fn main() {
let _a = Noisy("first");
let _b = Noisy("second");
let _ = Noisy("dropped at once"); // _ does not bind: the temporary is dropped here
} // prints: drop dropped at once, drop second, drop first
Drop scopes
Each variable or temporary belongs to a drop scope. The scopes are the entire
function, each statement, each expression, each block (including the function
body) and each match arm. When control leaves a scope, its variables
are dropped in reverse order. for, if and
while behave as the match, loop and
break forms they are equivalent to. Variables bound by a
match arm or a pattern-matching guard belong to that arm’s scope.
Temporary scopes
A value expression used where a place is needed creates a temporary, whose scope is the smallest of these enclosing it:
- The entire function
- A statement
- The body of an
if,whileorloop, and theelseblock of anif - The non-pattern condition of an
iforwhile, or a non-patternmatchguard condition - The pattern-matching guard and body of a
matcharm - Each operand of a lazy boolean expression
- The pattern-matching conditions and consequent of an
if(edition 2024) - The pattern-matching condition and loop body of a
while - The entire tail expression of a block (edition 2024)
The scrutinee of a match is not a temporary scope, so a temporary in
it lives until the end of the enclosing statement.
Edition 2024 changed two cases: if let temporaries are dropped before
the else block runs, and temporaries in a block’s tail expression are
dropped right after the expression is evaluated, before the block’s locals. See
Editions and
versions.
Temporary lifetime extension
A temporary in a let initialiser can have its scope extended to the
enclosing block, and in a const or static initialiser to
the end of the program. Extension applies when the initialiser is an extending
expression: the operand of a borrow, of an array, cast, braced struct, tuple or
tuple struct constructor, the final expression of a block (except an
async block), or an if/else or
match arm body. An extending pattern such as ref x extends
the initialiser’s temporary too.
let x = &temp(); // extended
let x = Some(&temp()); // extended: variant argument
let x = { [Some(&temp())] }; // extended: the final expression of a block
let x = core::convert::identity(&temp()); // error: a function argument is not extended
let x = (&temp()).use_temp(); // error: a method receiver is not extended
let x = match &temp() { x => x }; // error: a scrutinee is not extended
A value that could be a constant, has no interior mutability or destructor, and
is borrowed may be promoted to a 'static slot: &None
has type &'static Option<_>.
When destructors do not run
std::mem::forgetandstd::mem::ManuallyDropstop a destructor running. This is safe, so unsafe code must not rely on destructors running for soundness.- Destructors do not run when the process ends without unwinding: on
std::process::exitorstd::process::abort, or a panic with the abort strategy. - A static’s destructor is never run.