'static appears in two related but different positions, and mixing them up
leads to a common misreading. For example, thread::spawn requires its
closure type F to satisfy F: Send + 'static. That bound does not mean a
thread can only use string literals or data that lives until the program
exits.
This article separates the two uses:
- a lifetime on a reference, as in
&'static str, and - an outlives bound on a type, as in
T: 'static.
A lifetime is the region of the program during which a borrow must stay
valid. 'static is the longest one: the rest of the program's execution.
Meaning one: &'static T is a reference
let s: &'static str = "hello";
This is a reference whose referent (the data it points to) stays valid for the rest of the program. The reference itself is an ordinary local variable; only the data it points to has to remain valid that long. Common sources:
- String literals.
"hello"has type&'static str. The text is stored as part of the compiled program, not on the stack or heap at run time. staticitems. Astatichas one storage location that lasts for the whole program, so&NAMEcan be a&'staticreference.Box::leak. It turns a heap allocation into a reference that is never freed automatically. See the last section.
So &'static T describes how long the referenced data is guaranteed to stay
valid.
Meaning two: T: 'static is a bound
fn needs_static<T: 'static>(_t: T) {}
This is a constraint on a type, not an instruction to keep a value alive.
T: 'static reads "T outlives 'static": the type must not contain any
borrowed data that could become invalid before the program ends. Types with
no references or lifetime parameters, such as String, Vec<u8> and i64,
satisfy it. A value of such a type can still be dropped at the end of an
ordinary local scope:
fn needs_static<T: 'static>(_t: T) {}
fn main() {
let s = String::from("noon");
needs_static(s); // compiles; `s` is moved in and dropped when the call ends
}
A String does point at heap memory, but it owns that memory. The bound is
about borrowed data, so owning a heap buffer does not stop a type from being
'static.
Owning the outer container is not enough, though, because a container can hold references to someone else's data. This example does not compile:
fn needs_static<T: 'static>(_t: T) {}
fn main() {
let source = String::from("local");
let owned = vec![source.as_str()];
needs_static(owned); // error[E0597]: `source` does not live long enough
}
owned has type Vec<&str> where the &str borrows source. The vector
owns its own buffer, but its elements are borrows. For Vec<&'x str> to
satisfy 'static, the borrow lifetime 'x would have to be 'static, and
source is dropped at the end of main. Moving the vector does not extend
that borrow. Two variations do satisfy the bound:
Vec<String>owns its strings, so it contains no borrows.Vec<&'static str>, for examplevec!["a", "b"], contains references, but they are already valid for the rest of the program.
In short: T: 'static does not mean "this value lives forever". It means
"this type does not depend on any borrow shorter than 'static".
Check your understanding
thread::spawn requires F: Send + 'static, where F is the closure's type. What does the 'static part rule out, and what does it still allow?
Show answer
Hide answer
It rules out closures that capture references to data that might be freed
while the thread is still running, such as a reference to a local variable of
the function that called spawn. It still allows closures that capture owned
values, such as a String, even though those values are dropped when the
thread finishes. The separate Send bound must also hold: every captured
value has to be safe to move to another thread.
let s = String::from("hi");
let h = std::thread::spawn(move || println!("{s}")); // compiles
h.join().unwrap();
move makes the closure take ownership of s. The closure's type now
contains a String, which satisfies 'static, so the bound holds. The
string is dropped when the closure is dropped after the thread finishes.
Moving a container of borrows into the closure does not help. Capturing
vec![s.as_str()] with move still fails with E0597, for the same reason as
the needs_static example above.
Why the bound exists: a thread created by thread::spawn can keep running
after the function that spawned it returns, because nothing requires you to
call join. If the closure held a &str into one of that function's locals,
the local could be freed while the thread still reads it. F: 'static rules
out such references.
When you do want to borrow local data from threads, use std::thread::scope.
Every thread spawned inside the scope is joined before scope returns, so the
compiler allows those threads to borrow data that outlives the scope call. The
'static requirement belongs to thread::spawn, not to threads in general.
Reference types in const and static items
In the type of a const or static item, an elided reference lifetime means
'static:
const GREETING: &str = "hi"; // type: &'static str
static NAME: &str = "world"; // type: &'static str
Writing 'static explicitly is still valid Rust, and rustc accepts it. Clippy
reports it with the redundant_static_lifetimes lint, which is in the
warn-by-default clippy::style group:
const G: &'static str = "hi";
error: constants have by default a `'static` lifetime
--> src/main.rs:1:11
|
1 | const G: &'static str = "hi";
| -^^^^^^^---- help: consider removing `'static`: `&str`
|
= help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#redundant_static_lifetimes
= note: `-D clippy::redundant-static-lifetimes` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::redundant_static_lifetimes)]`
It appears as an error here because this site runs Clippy with
-D warnings, which turns every enabled warning into an error. For a
static item the message says "statics have by default a 'static
lifetime". Write &str in these item types.
Trait objects have a default lifetime too
A trait object such as dyn Fn() -> usize can capture borrowed data, so it
also has a lifetime bound, written dyn Fn() -> usize + 'a. When you leave the
bound out, Rust picks a default. Inside Box in a type alias, the default is
'static:
type Handler = Box<dyn Fn() -> usize>;
// means: Box<dyn Fn() -> usize + 'static>
So a Handler cannot hold a closure that borrows a function argument. The
same 'static default applies to Box<dyn Fn() -> usize> written in a
function signature. A function that returns a closure borrowing its argument
must connect the object's lifetime to that argument:
fn counter<'a>(text: &'a str) -> Box<dyn Fn() -> usize + 'a> {
Box::new(move || text.len())
}
If you remove + 'a, the compiler reports "lifetime may not live long
enough": the returned closure borrows text, so it cannot meet the default
'static bound for every possible input. Writing + '_ instead of + 'a
also compiles. '_ asks for the usual lifetime-elision rules, which here
pick the lifetime of the single reference argument. It does not turn off
lifetime checking.
Inside a function body, a type annotation on a let binding is treated
differently: the object's lifetime is inferred from how the value is used.
This compiles even though the closure borrows the local text:
let text = String::from("hi");
let count: Box<dyn Fn() -> usize> = Box::new(|| text.len());
assert_eq!(count(), 2);
So do not read every Box<dyn Trait> as 'static. The default depends on the
containing type, any lifetime bounds on the trait, and whether the type is
written in a signature or item or inside a function body. The full rules are
in the Rust Reference under
default trait-object lifetimes.
Check your understanding Consider each numbered call on its own, as if the other three were not there. Which compile? For each failure, say which of the two meanings is involved. Show answer Hide answer
fn a<T: 'static>(_t: T) {}
fn b(_s: &'static str) {}
fn main() {
let owned = String::from("x");
let literal = "y";
let borrowed: &str = &owned;
a(owned.clone()); // 1
a(borrowed); // 2
b(literal); // 3
b(borrowed); // 4
}
1 compiles. owned.clone() is a String, which contains no borrows, so
String: 'static holds. This is the bound meaning.
2 fails. borrowed has type &'x str, where 'x is the lifetime of the
borrow &owned. The bound &'x str: 'static requires 'x to be 'static,
but owned is dropped at the end of main. The compiler reports
E0597, "owned does not live long enough". This is the bound meaning:
the type contains a short-lived reference.
3 compiles. literal is a string literal, so its type really is
&'static str. This is the reference meaning.
4 fails with the same E0597 error. This time the requirement comes from
the reference meaning: b's parameter type is &'static str, and a borrow of
owned cannot last that long.
Calls 2 and 4 fail for the same underlying reason, a short-lived borrow used
where a 'static one is required, but they reach it differently. Call 2
goes through a bound on a generic type; call 4 goes through the lifetime
written on a reference type. If you compile the whole program at once, rustc
reports a single E0597 at &owned, because both calls make the same borrow
need to last for 'static; deleting either call still leaves that error.
&'static mut T and Box::leak
&'static mut T is also a valid type. Like any &mut, it is an exclusive
reference: while it is in use, nothing else can read or write the value.
One safe way to get one is Box::leak:
let r: &'static mut String = Box::leak(Box::new(String::from("hi")));
r.push('!');
assert_eq!(r, "hi!");
Box::leak gives up automatic cleanup: the allocation is not freed when
r goes out of scope. That is acceptable for a value created once and used
until the program exits, such as configuration loaded at startup. Calling it
repeatedly, for example once per request, leaks memory each time.
Summary
| you see | it means |
|---|---|
&'static T |
a reference whose data stays valid for the rest of the program |
T: 'static |
the type contains no borrow shorter than 'static; a value of the type can still be dropped at any point |
const X: &str or static X: &str |
implicitly &'static str; Clippy flags an explicit 'static |
Box<dyn Fn()> in a type alias or function signature |
defaults to Box<dyn Fn() + 'static> |
Box<dyn Fn()> in a let annotation inside a function body |
the object lifetime is inferred from use |
std::thread::spawn |
the closure must be Send + 'static; use std::thread::scope to borrow locals |