once_cell / LazyLock & dashmap
LazyLock (stdlib since Rust 1.80) initializes globals on first access. dashmap is a concurrent HashMap for caches and registries without a global mutex.
Recipe
use std::sync::LazyLock;
static CONFIG: LazyLock<String> = LazyLock::new(|| {
std::env::var("APP_NAME").unwrap_or_else(|_| "app".into())
});dashmap = "6"use dashmap::DashMap;
use std::sync::Arc;
fn main() {
let cache: Arc<DashMap<String, u64>> = Arc::new(DashMap::new());
cache.insert("user:1".into(), 42);
assert_eq!(*cache.get("user:1").unwrap(), 42);
}When to reach for this:
- Process-wide HTTP clients, regex sets, config snapshots
- In-memory rate-limit or session tables (with eviction policy)
- Registry of named services in long-lived daemons
Working Example
use std::sync::LazyLock;
use dashmap::DashMap;
static METRICS: LazyLock<DashMap<&'static str, u64>> =
LazyLock::new(DashMap::new);
fn inc(name: &'static str) {
*METRICS.entry(name).or_insert(0) += 1;
}
fn main() {
inc("requests");
inc("requests");
assert_eq!(*METRICS.get("requests").unwrap(), 2);
}What this demonstrates:
LazyLockreplacesonce_cell::sync::Lazyfor new codeDashMapallows concurrentinsert/get- Prefer
Arc<DashMap>in Axum state for testability
Deep Dive
LazyLock vs once_cell
| Tool | Status |
|---|---|
std::sync::LazyLock | Preferred on Rust 1.80+ |
once_cell | Still used in older MSRV crates |
tokio::sync::OnceCell | Async initialization |
DashMap vs RwLock<HashMap>
DashMap shards locks internally; better for many readers/writers on independent keys. Single-key hot spots still contend.
Gotchas
- Mutable static without sync - compile error or UB. Fix:
LazyLock,Mutex, orDashMap. - DashMap as authoritative store - data lost on restart. Fix: persist to DB; cache only.
- Unbounded cache growth - OOM. Fix: TTL, LRU layer, or
mokacrate. - LazyLock panics in init - fails at first access. Fix: return
Resultfrom inner setup or useOnceLock<Result<T>>. - Iterating DashMap while mutating - deadlocks possible. Fix: snapshot keys first.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
RwLock<HashMap> | Low contention | Many concurrent keys |
moka / cached | TTL caches | Simple counters |
tokio::sync::Mutex<HashMap> | Async-only single lock | High read concurrency |
FAQs
Still need once_cell on 1.97?
Rarely for LazyLock. once_cell remains for ecosystem crates not yet migrated.
DashMap and Tokio?
Sync API is fine for short critical sections; avoid long awaits while holding shard locks.
Testing globals?
Prefer injecting Arc<DashMap> via app state instead of statics in tests.
LazyLock and MSRV?
Rust 1.97 includes LazyLock. Document MSRV if supporting older compilers with once_cell.
Related
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.