188. LazyLock::from — Skip the Closure When You Already Have the Value

Sometimes your “lazy” value isn’t lazy at all — a test or a CLI flag hands it to you up front. Rust 1.96 stabilized From<T> for LazyLock<T>, so you can build an already-initialized lock straight from the value.

The old workaround was to wrap the known value in a move closure anyway:

1
2
// Pretend-lazy: the value sits captive until the first deref
let lock = LazyLock::new(move || url);

It compiles, but the lock reports “not initialized” until someone derefs it — even though you had the value the whole time. As of Rust 1.96, LazyLock::from (or .into()) builds the lock pre-initialized:

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

let eager: LazyLock<u32> = LazyLock::from(42);

// Initialized immediately — no deref needed first
assert_eq!(LazyLock::get(&eager), Some(&42));

The practical win is mixing eager and lazy at runtime. From produces the default F = fn() -> T parameter — the same type a non-capturing closure coerces to — so both branches unify:

1
2
3
4
5
6
7
8
fn api_url(cli_override: Option<String>) -> LazyLock<String> {
    match cli_override {
        // Value already known: initialized, closure never exists
        Some(url) => LazyLock::from(url),
        // Computed on first use, as usual
        None => LazyLock::new(|| std::env::var("API_URL").unwrap()),
    }
}

Before 1.96 the Some arm needed LazyLock::new(move || url) — deferring initialization for no reason and making LazyLock::get lie to you in tests.

The single-threaded sibling From<T> for LazyCell<T> landed in the same release, so the trick works in both std::sync and std::cell flavors.

← Previous 187. fmt::Write — Stop Allocating a Temp String Just to Append It Next → 189. str::char_indices — Slice a String Without Panicking on Non-ASCII