'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.
// 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.
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.
// 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.
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
}# 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:
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.
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 'staticTrait-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.
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
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
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
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
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
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
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
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
- The Rust Book – The Static Lifetime
- Rust Reference – Static Items
- std::sync::OnceLock
- Common Rust Lifetime Misconceptions