'static ist eine besondere Lifetime — die einzige, die einen festen Namen hat (alle anderen sind Variablen wie 'a). Sie bedeutet: so lange wie das Programm. Werte mit 'static-Lifetime sind im Binary fest hinterlegt oder leben in statischem Speicher. String-Literale, Konstanten, und über Box::leak allozierter Speicher haben 'static. Aber: der Bound T: 'static heißt etwas anderes als „ist 'static" — er sagt, dass der Typ keine geliehenen Daten mit kürzerer Lifetime hält. Diese Unterscheidung verwirrt viele und ist der Schlüssel zum Verständnis vieler Thread- und Async-APIs.

Was 'static bedeutet

'static ist die Lifetime, die die gesamte Programm-Laufzeit umfasst. Eine Referenz mit 'static-Lifetime ist von Programmstart bis -ende gültig.

Rust Werte mit 'static
// String-Literale haben 'static
let s: &'static str = "Hello";

// Konstanten haben 'static
const NAME: &'static str = "Rust";
const NUMBER: i32 = 42;            // i32 selbst hat keine Lifetime, aber const lebt 'static

// Static-Variablen haben 'static
static COUNTER: i32 = 0;

// Box::leak gibt eine 'static-Referenz auf heap-allozierte Daten
let leaked: &'static str = Box::leak(Box::new(String::from("leaked")));

fn main() {
    println!("{s}, {NAME}, {NUMBER}, {COUNTER}, {leaked}");
}

Diese Werte sind während der gesamten Programm-Laufzeit gültig. Sie werden nie dropped, weil sie keinen Owner haben, der sie dropped — sie sind im statischen Speicher (Binary, Heap mit Leak).

String-Literale

Der häufigste 'static-Wert ist das String-Literal.

Rust String-Literal
fn get_message() -> &'static str {
    "Hello, World!"     // String-Literal → 'static
}

fn main() {
    let m = get_message();
    println!("{m}");
}

Der String "Hello, World!" liegt im Binary (im read-only data section). Er existiert ab Programmstart, hat keinen Owner, ist immer gültig. Die Lifetime 'static ist daher korrekt.

Wichtig: nur Literale sind automatisch 'static. Ein String-Wert, der zur Laufzeit gebaut wird (String::from(...)), hat keine 'static-Lifetime — er hat eine, die an seinen Scope gebunden ist.

Konstanten und Statics

Konstanten und static-Variablen sind klare Beispiele für 'static-Werte.

Rust const vs static
// const: zur Compile-Zeit ausgewertet, inline-eingesetzt
const PI: f64 = 3.14159265358979;
const APP_NAME: &str = "MyApp";          // &'static str implizit

// static: zur Laufzeit angelegt, fester Speicher-Ort, 'static-Lifetime
static MAX_CONNECTIONS: u32 = 100;
static GREETING: &str = "Hello";          // &'static str implizit

fn main() {
    println!("π ≈ {PI}");
    println!("{APP_NAME} kann {MAX_CONNECTIONS} Verbindungen");
    println!("{GREETING}");
}

const: Compile-Zeit-Wert, der überall inline eingesetzt wird. Keine fester Speicher-Ort. static: Laufzeit-Wert mit festem Speicher-Ort. Hat genau eine Adresse, lebt 'static.

Für Referenz-Typen (&str, &[u8]) wird die 'static-Lifetime implizit angenommen. Für die meisten Anwendungen ist diese Unterscheidung nicht wichtig — beide verhalten sich aus Lifetime-Sicht wie 'static.

T: 'static — der Bound

Die wichtigste Subtilität: T: 'static bedeutet nicht „T ist 'static" im Sinne von „T ist im Binary". Der Bound sagt:

T enthält keine geliehenen Daten mit kürzerer Lifetime als 'static.

Anders gesagt: T ist entweder owned (wie String, Vec, eigene Structs ohne Refs), oder T enthält nur Referenzen mit 'static-Lifetime.

Rust T: 'static erfüllt
fn check<T: 'static>(_x: T) {
    println!("Erfüllt T: 'static");
}

fn main() {
    check(42);                              // i32 — owned, kein Borrow → 'static
    check(String::from("hi"));              // String — owned → 'static
    check(vec![1, 2, 3]);                   // Vec<i32> — owned → 'static
    check("hello");                          // &'static str → 'static
}
Rust T: 'static NICHT erfüllt
# fn check<T: 'static>(_x: T) {}
fn main() {
    let local = String::from("local");
    let r: &String = &local;          // r hat KURZE Lifetime
    // check(r);    // FEHLER: &String mit non-static Lifetime erfüllt T: 'static nicht
    let _ = r;
}

Eine Referenz auf eine lokale Variable hat keine 'static-Lifetime — sie ist gebunden an den lokalen Scope. Daher erfüllt &local den Bound T: 'static nicht.

Das ist der zentrale Punkt: T: 'static heißt „T hat keine kurzlebigen Borrows in sich" — nicht „T ist ein statischer Wert im Binary".

Wo T: 'static auftaucht

Der Bound T: 'static ist in vielen APIs vorhanden:

Rust Thread-Spawn
use std::thread;

// thread::spawn fordert F: Send + 'static
fn main() {
    let owned = String::from("owned data");

    // OK: owned wird move-captured, hat eigene Lifetime gleich Thread-Leben
    thread::spawn(move || {
        println!("{owned}");
    }).join().unwrap();

    let local = String::from("local");
    // FEHLER: Closure borgt local — non-static
    // thread::spawn(|| {
    //     println!("{local}");
    // }).join().unwrap();
    let _ = local;
}

thread::spawn will sicherstellen, dass der Thread alle Daten überleben kann, die er nutzt. Daher fordert er F: 'static — die Closure darf keine kurzlebigen Borrows enthalten. Mit move werden Variablen in die Closure verschoben (owned), nicht geliehen — Bound erfüllt.

Rust Trait-Objekt mit 'static
use std::fmt::Display;

// Box<dyn Display + 'static> — Trait-Objekt darf keine kurzen Borrows haben
fn boxed(items: Vec<Box<dyn Display>>) {
    for i in items {
        println!("{i}");
    }
}
// Beachte: Box<dyn Trait> ohne explizite Lifetime defaultet zu 'static

Trait-Objekte in Boxes haben default 'static-Lifetime. Wenn du borrowed Daten darin packen willst, musst du explizit eine andere Lifetime angeben (Box<dyn Display + 'a>).

Box::leak — manuell 'static erzeugen

Manchmal brauchst du eine 'static-Referenz auf heap-allozierte Daten. Box::leak ist das Werkzeug.

Rust Box::leak
fn main() {
    // Heap-String, Lifetime gebunden an Scope
    let s = String::from("dynamic");

    // Box::leak nimmt die Box, gibt eine 'static-Referenz zurück
    // Speicher wird nie freigegeben — "geleakt"
    let leaked: &'static str = Box::leak(s.into_boxed_str());

    // Jetzt kann leaked überall genutzt werden, wo 'static gefordert ist
    std::thread::spawn(move || {
        println!("{leaked}");
    }).join().unwrap();
}

Box::leak ist eine bewusste Memory-Leak-Operation. Der Speicher wird nie freigegeben — er lebt bis Programmende. Das ist akzeptabel für: einmalige Initialisierung beim Start, Config-Daten, kleine Strings die das ganze Programm gebraucht werden.

Nicht akzeptabel für: regelmäßig erzeugte Daten — sonst wächst der Speicher unbegrenzt.

Praxis: 'static im Alltag

Config-Konstanten

Rust Config
pub const VERSION: &str = "1.2.3";
pub const MAX_RETRIES: u32 = 3;
pub const TIMEOUT_SEC: u64 = 30;

pub static APP_NAME: &str = "MyService";

fn main() {
    println!("{APP_NAME} v{VERSION}");
    println!("Max retries: {MAX_RETRIES}, Timeout: {TIMEOUT_SEC}s");
}

Klassische Config-Konstanten. Alle 'static, im ganzen Programm gültig.

Lazy-Init mit OnceLock

Rust OnceLock
use std::sync::OnceLock;

static CONFIG: OnceLock<String> = OnceLock::new();

fn get_config() -> &'static str {
    CONFIG.get_or_init(|| {
        // Wird genau einmal aufgerufen
        String::from("loaded-from-file")
    })
}

fn main() {
    println!("{}", get_config());
    println!("{}", get_config());      // Zweiter Aufruf: gleiche Daten
}

OnceLock (Stand 2026 in Stdlib) erlaubt lazy-initialisierte 'static-Werte. Wert wird beim ersten Zugriff erzeugt, danach 'static gültig.

Thread-Spawn mit owned-Daten

Rust Thread mit owned
use std::thread;

fn main() {
    let data = vec![1, 2, 3, 4, 5];

    // move captures data — Closure wird 'static (kein Borrow)
    let handle = thread::spawn(move || {
        let sum: i32 = data.iter().sum();
        println!("Sum: {sum}");
        sum
    });

    let result = handle.join().unwrap();
    println!("Got: {result}");
}

move-Closure übernimmt Ownership, ist daher 'static. Thread kann starten, ohne dass es Lifetime-Probleme gibt.

Bound an Threading-Helfer

Rust Custom Thread-Helper
use std::thread;
use std::fmt::Debug;

pub fn parallel_print<T: Debug + Send + 'static>(items: Vec<T>) {
    let handles: Vec<_> = items.into_iter().enumerate().map(|(i, item)| {
        thread::spawn(move || {
            println!("Thread {i}: {item:?}");
        })
    }).collect();

    for h in handles {
        h.join().unwrap();
    }
}

fn main() {
    parallel_print(vec![1, 2, 3]);
    parallel_print(vec!["a", "b", "c"]);      // &'static str — auch OK
}

T: Send + 'static ist die typische Bound-Kombination für Thread-übertragene Daten. Owned-Typen erfüllen es, geliehene mit kurzen Lifetimes nicht.

Static-Slice-Tabelle

Rust Statische Tabelle
static WOCHENTAGE: &[&str] = &[
    "Montag", "Dienstag", "Mittwoch",
    "Donnerstag", "Freitag", "Samstag", "Sonntag"
];

fn name_des_tages(idx: usize) -> Option<&'static str> {
    WOCHENTAGE.get(idx).copied()
}

fn main() {
    for i in 0..7 {
        if let Some(name) = name_des_tages(i) {
            println!("{i}: {name}");
        }
    }
}

Statische Lookup-Tabelle. Alle Slice-Elemente sind 'static-Refs, der Slice selbst ist 'static. Lookup-Funktion gibt 'static-Refs zurück.

Box::leak für Config-Init

Rust Leak für Config
fn load_config_from_env() -> &'static str {
    let value = std::env::var("APP_CONFIG")
        .unwrap_or_else(|_| String::from("default"));
    // Beim Start einmal geleakt — Config gilt für gesamtes Programm
    Box::leak(value.into_boxed_str())
}

fn main() {
    let config = load_config_from_env();
    // config kann überall genutzt werden, auch in Threads
    std::thread::spawn(move || {
        println!("Config: {config}");
    }).join().unwrap();
}

Pragma: einmal beim Start leaken, danach 'static. Akzeptabel für nicht-wachsende Daten.

Trait-Objekt mit Lifetime vs. ohne

Rust Trait-Obj Lifetime
use std::fmt::Display;

// Standardform — Box<dyn> default 'static
fn boxed_static(items: Vec<Box<dyn Display>>) {
    for i in items { println!("{i}"); }
}

// Mit kurzer Lifetime — explizit
fn boxed_borrowed<'a>(items: Vec<Box<dyn Display + 'a>>) {
    for i in items { println!("{i}"); }
}

fn main() {
    // OK: i32 ist 'static
    boxed_static(vec![Box::new(42)]);

    // Erfordert explizite Lifetime, weil &local nicht 'static
    let local = 42;
    boxed_borrowed(vec![Box::new(&local)]);
}

Trait-Objekte in Boxes: default ist 'static. Wenn du borrowed Daten unterbringen willst, brauchst du explizite Lifetime-Annotation.

Interessantes

'static = Programm-Laufzeit lang.

Die längstmögliche Lifetime. Werte sind im Binary (Literale, const) oder im statischen Speicher (static). Box::leak überträgt heap-Daten in 'static.

String-Literale haben 'static.

"hello" ist immer &'static str. Im read-only data section des Binaries, ab Programmstart gültig, nie dropped.

const und static implizieren 'static.

Werte von Konstanten und Statics leben so lange wie das Programm. Referenz-Typen darin haben automatisch 'static.

T: 'static Bound bedeutet NICHT „T ist im Binary“.

Sondern: „T enthält keine geliehenen Daten mit kürzerer Lifetime“. Owned-Typen (String, Vec, Box) erfüllen das automatisch.

T: 'static ist Voraussetzung für Thread::spawn.

Damit der Thread Daten überleben kann. move-Closures mit owned-Daten erfüllen es; geliehene mit kurzer Lifetime nicht.

Box::leak — manuell 'static erzeugen.

Heap-Daten werden nie dropped, leben bis Programmende. Akzeptabel für einmalige Init beim Start, nicht für regelmäßig erzeugte Daten (Memory-Leak).

OnceLock für lazy 'static-Init.

Stdlib seit Rust 1.70. Wert wird beim ersten Zugriff erzeugt, danach 'static. Thread-safe, ohne Lock-Overhead nach erster Init.

Box defaultet auf 'static.

Wenn du borrowed Daten unterbringen willst, musst du explizit Box<dyn Trait + 'a> schreiben. Sonst akzeptiert der Compiler nur 'static-Daten.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Lifetimes

Zur Übersicht