The 'static lifetime in Rust (&'static str and T: 'static)
What 'static really means in Rust: &'static references vs T: 'static bounds, why thread::spawn needs 'static, and why String is 'static.
'static is the longest lifetime: the whole run of the program. It shows up in two forms that mean different things, and mixing them up is a common source of confusion.
1. &'static T: a reference valid forever
A &'static reference points at data that will never be freed:
- String literals:
let s: &'static str = "hello";The text is stored in the program binary. staticitems:static GREETING: &str = "hi";andstatic LIMITS: [u32; 3] = [1, 2, 3];- Constants promoted by the compiler:
let r: &'static i32 = &42; - Leaked heap data:
Box::leak(Box::new(value))returns a&'static mut Tby never freeing it (occasionally used for global config read once at startup).
2. T: 'static: a type with no short-lived borrows
As a bound, T: 'static does not mean "lives forever". It means "the type contains no references that could expire", so a value of that type can be kept as long as the holder likes.
String,Vec<u8>,i32,Box<dyn Error + Send + Sync>,Arc<Mutex<T>>: all'static, because they own their data.&'static str:'static.&'a strfor some shorter'a, or a struct holding one: not'static.
A String created a moment ago satisfies T: 'static, and it's dropped whenever its owner is done with it.
Where you meet T: 'static
thread::spawn requires its closure to be 'static because the new thread might outlive the function that started it. Borrowing a local fails:
use std::thread;
fn main() {
let names = vec!["Ferris", "Corro"];
let handle = thread::spawn(|| {
println!("{}", names.len());
});
handle.join().unwrap();
}
error[E0373]: closure may outlive the current function, but it borrows `names`, which is owned by the current function
Fixes: move || to give the thread ownership, Arc to share, or thread::scope to borrow safely (scoped threads are guaranteed to finish first). tokio::spawn, Box<dyn Any> and many callback registries have the same bound.
The same check applies to any generic function with a 'static bound:
fn store<T: 'static>(_value: T) {}
fn main() {
let name = String::from("Ferris");
store(&name);
}
error[E0597]: `name` does not live long enough
rustc adds "argument requires that name is borrowed for 'static". Passing name itself (owned) works.
Common mistakes
- Adding
'staticto make an error go away. Writingfn parse(input: &'static str)compiles, but now callers can only pass literals; a line read from a file won't work. Use a normal lifetime, or own the data. - Thinking
T: 'staticmeans leaked memory. It doesn't; owned values satisfy it and are freed normally. static mutfor globals. Reading or writing it needsunsafe, and taking references to it is an error by default in the 2024 edition. Use atomics,Mutex, orOnceLock/LazyLockfor global state.
Example
use std::sync::OnceLock;
use std::thread;
static VERSION: &str = "1.4.2";
static LEVELS: [&str; 3] = ["info", "warn", "error"];
fn level_name(i: usize) -> &'static str {
LEVELS.get(i).copied().unwrap_or("unknown")
}
fn config() -> &'static String {
static CONFIG: OnceLock<String> = OnceLock::new();
CONFIG.get_or_init(|| format!("arena v{VERSION}"))
}
fn keep<T: 'static + std::fmt::Debug>(value: T) -> String {
format!("kept {value:?}")
}
fn main() {
let literal: &'static str = "literals are 'static";
println!("{literal}");
println!("{} {}", level_name(1), level_name(7));
println!("{}", config());
let owned = String::from("a fresh String is 'static too");
println!("{}", keep(owned));
let names = vec![String::from("Ferris"), String::from("Corro")];
let handle = thread::spawn(move || names.len());
println!("thread counted {}", handle.join().unwrap());
}
Output:
literals are 'static
warn unknown
arena v1.4.2
kept "a fresh String is 'static too"
thread counted 2