Threads

Thread-Local Storage

Thread-Local Storage (TLS) gives each operating-system thread its own isolated, lazily initialized instance of a static variable. Declared with the std::thread_local! macro, a thread-local variable has type std::thread::LocalKey<T>, requires zero cross-thread synchronization (T does not need to implement Sync or Send), and runs T's Drop destructor automatically when each individual thread exits.

How thread_local! Works Across Threads

Declaration:
  thread_local! {
      static SCRATCH_BUF: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
  }

Runtime Memory Layout (Zero Contention Between Threads!):
  +-----------------------------------+   +-----------------------------------+
  | Thread 1 TLS Segment              |   | Thread 2 TLS Segment              |
  |   SCRATCH_BUF: RefCell<Vec<u8>>   |   |   SCRATCH_BUF: RefCell<Vec<u8>>   |
  |   (Allocated & dropped by T1)     |   |   (Allocated & dropped by T2)     |
  +-----------------------------------+   +-----------------------------------+

Accessing LocalKey<T> (And Special Convenience Methods on Cell and RefCell)

Because a thread-local variable is destroyed when the current thread terminates, LocalKey<T> cannot hand out a bare 'static reference &'static T (which could be sent to another thread that outlives the current thread!). Instead, access is scoped to a closure via .with(|val: &T| ...)—with direct convenience methods unlocked when T is Cell<U> or RefCell<U>:

LocalKey Type Method Signature / Behavior
Any LocalKey<T> .with(f) fn with<F, R>(&'static self, f: F) -> R where F: FnOnce(&T) -> R
Any LocalKey<T> .try_with(f) Returns Err(AccessError) instead of panicking if called after the TLS value was already destroyed during thread shutdown
LocalKey<Cell<T>> .get() (T: Copy), .set(val), .take() (T: Default), .replace(val) Directly reads or writes the thread-local Cell<T> without needing a .with(|c| ...) closure!
LocalKey<RefCell<T>> .with_borrow(f), .with_borrow_mut(f), .set(val), .take() Directly borrows &T or &mut T inside f without calling .borrow() / .borrow_mut() manually!

Using const { ... } Initializers and LocalKey<RefCell<T>> Methods

Using const { ... } in a thread_local! initializer enables fast, zero-branch static TLS initialization in the binary image:

use std::cell::{Cell, RefCell};
use std::thread;

thread_local! {
    static NEXT_REQUEST_ID: Cell<u64> = const { Cell::new(1) };
    static THREAD_LOG: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) };
}

fn record_event(action: &str) {
    // Direct `.get()` and `.set()` on `LocalKey<Cell<u64>>`:
    let req_id = NEXT_REQUEST_ID.get();
    NEXT_REQUEST_ID.set(req_id + 1);

    // Direct `.with_borrow_mut()` on `LocalKey<RefCell<Vec<String>>>`:
    THREAD_LOG.with_borrow_mut(|log| {
        log.push(format!("[req #{req_id}] {action}"));
    });
}

fn main() {
    record_event("main_init");
    record_event("main_ready");

    let worker = thread::spawn(|| {
        // Worker thread starts with its own fresh `NEXT_REQUEST_ID = 1` and empty `THREAD_LOG`:
        record_event("worker_job_a");
        THREAD_LOG.take()
    });

    let worker_events = worker.join().unwrap();
    let main_events = THREAD_LOG.take();

    println!("main thread log:   {:?}", main_events);
    println!("worker thread log: {:?}", worker_events);
}
main thread log:   ["[req #1] main_init", "[req #2] main_ready"]
worker thread log: ["[req #1] worker_job_a"]