Threads
Thread Safety and Lifetimes
In concurrent Rust, thread safety (Send and Sync) and lifetimes ('static vs. 'env) are orthogonal axes that work together:
SendandSyncverify how a type may be accessed across threads (exclusive transfer vs. concurrent shared access).- Lifetimes (
T: 'a) verify how long the underlying memory remains allocated relative to the threads accessing it.
Two Orthogonal Compile-Time Checks on Every Thread Spawn:
Lifetime Axis (How Long Memory Lives)
-------------------------------------
| 'static (Owned / Arc / &'static) | 'env (Stack Borrow)
-------------------+------------------------------------+----------------------
Send + Sync | `thread::spawn` + `thread::scope` | `thread::scope` ONLY
(e.g. Vec, String) | (Can move OR share via Arc/&'env) | (Can borrow &'env T)
-------------------+------------------------------------+----------------------
Send + !Sync | `thread::spawn` + `thread::scope` | `thread::scope` ONLY
(e.g. RefCell<T>) | (Move uniquely only; no Arc<T>!) | (Move uniquely / &mut)
-------------------+------------------------------------+----------------------
!Send + !Sync | Confined to creating thread (`thread_local!` only)
(e.g. Rc<T>) | (Cannot cross thread boundaries regardless of lifetime!)
Clarifying the Misconception Around T: 'static
In std::thread::spawn<F, T>(f: F) where F: FnOnce() -> T + Send + 'static, the bound F: 'static does NOT mean F must be a static global variable or live until the end of the program!
A type T satisfies T: 'static if and only if T contains no non-'static borrowed references (meaning its validity is not restricted by any shorter lexical borrow 'a):
| Type | Satisfies T: 'static? |
Why |
|---|---|---|
String, Vec<u8>, u64, Box<T> |
Yes! | Owns its heap/stack data completely; can be moved into thread::spawn and dropped at any time |
Arc<Vec<u8>>, Arc<Mutex<State>> |
Yes! | Reference-counted shared ownership; valid as long as the Arc clone lives |
&'static str ("literal") |
Yes! | Points to read-only binary segment valid for the entire program |
&'a str, View<'a> (where 'a != 'static) |
No (E0597) |
Borrows from a local stack frame 'a; can only be used in std::thread::scope |
Combining Owned 'static Data and Scoped 'env Borrows
use std::sync::Arc;
use std::thread;
fn main() {
// 1. Owned `String` satisfies `'static` -> can move into `thread::spawn`
// and be dropped immediately when the worker finishes:
let owned_task_name = String::from("ephemeral-owned-string");
let spawned = thread::spawn(move || {
println!("spawned thread owns and drops: {:?}", owned_task_name);
});
spawned.join().unwrap();
// 2. Shared `Arc<T>` satisfies `'static` -> can outlive local scopes:
let shared_config = Arc::new(String::from("shared-arc-config"));
// 3. Local stack slice `&[u32]` has non-`'static` lifetime `'env` ->
// used directly inside `thread::scope` alongside `shared_config`:
let stack_numbers = [10_u32, 20, 30];
thread::scope(|s| {
let cfg = Arc::clone(&shared_config);
let nums_ref: &[u32] = &stack_numbers;
s.spawn(move || {
println!("scoped thread reads cfg={:?} and stack slice={:?}", cfg, nums_ref);
});
});
}
spawned thread owns and drops: "ephemeral-owned-string"
scoped thread reads cfg="shared-arc-config" and stack slice=[10, 20, 30]