#136 May 13, 2026

136. LazyLock::force_mut — Mutate a Lazy Value Without Wrapping It in a Mutex

Got a LazyLock you own outright? With force_mut (stable in Rust 1.94), you can initialize and mutate it through &mut LazyLock — no Mutex, no RwLock, no locking dance.

The Problem

LazyLock is perfect for one-time initialization, but its Deref only hands out a shared reference. If you want to mutate the inner value, the textbook move is to wrap it in Mutex<T>:

1
2
3
4
5
6
7
use std::sync::{LazyLock, Mutex};

static CACHE: LazyLock<Mutex<Vec<String>>> = LazyLock::new(|| Mutex::new(Vec::new()));

fn add(s: String) {
    CACHE.lock().unwrap().push(s);
}

That’s the right answer for shared global state. But when you actually have exclusive ownership — a struct field, a builder, a test fixture — the Mutex is pure ceremony.

force_mut: Init and Mutate in One Step

If you have &mut LazyLock<T>, you already have exclusive access. Synchronization is moot. force_mut exploits that: it triggers initialization if needed, then hands you a plain &mut T.

1
2
3
4
5
6
7
8
9
use std::sync::LazyLock;

let mut tags = LazyLock::new(|| vec!["rust"]);

let v: &mut Vec<&'static str> = LazyLock::force_mut(&mut tags);
v.push("std");
v.push("lazy");

assert_eq!(*v, vec!["rust", "std", "lazy"]);

No Mutex, no .lock().unwrap(), no poisoning to handle. The init closure runs at most once, and from then on you can mutate freely through &mut.

get_mut: Mutate Only If Already Initialized

The sibling, LazyLock::get_mut, returns Option<&mut T> and won’t trigger init:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
use std::sync::LazyLock;

let mut counts: LazyLock<Vec<u32>> = LazyLock::new(|| vec![0u32; 8]);

// Hasn't been touched yet — get_mut returns None.
assert!(LazyLock::get_mut(&mut counts).is_none());

// Force initialization explicitly:
LazyLock::force_mut(&mut counts)[0] = 42;

// Now it's available without re-running the closure:
let slot = LazyLock::get_mut(&mut counts).unwrap();
slot[1] = 99;

assert_eq!(*slot, [42, 99, 0, 0, 0, 0, 0, 0]);

Useful when you’d rather skip work entirely if it never happened — “flush the cache on shutdown, but only if anyone built it.”

When to Reach for It

Pick force_mut whenever you own the LazyLock outright and would otherwise wrap it in Mutex<T> just to get mutation. It’s perfect for struct fields, test fixtures, builders, and anything else where you already have &mut to the container.

LazyCell::force_mut and LazyCell::get_mut ship the same shape for the single-thread cell — pick whichever matches your sync story.

← Previous 135. str::strip_prefix — Trim a Prefix Without Slicing by Hand