RustArena

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:

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.

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

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

Practice it