Some values are expensive to build and should only be built if something actually asks for them: a parsed config, a compiled regex, a lookup table. A static cannot help, because a static initializer has to be a constant expression evaluated at compile time.
OnceLock<T> is the answer. It is a slot that starts empty and can be filled exactly once:
static SETTINGS: OnceLock<Settings> = OnceLock::new();
fn settings() -> &'static Settings {
SETTINGS.get_or_init(build_settings)
}get_or_init is where the concurrency lives. If ten threads call it at the same moment on an empty lock, exactly one runs build_settings. The other nine block until it finishes and then get a reference to that same value. The initializer never runs twice, and no caller ever sees a half-built value.
Because the value can never be replaced, handing out &'static T is sound, and callers need no lock at all to read it.
LazyLock<T> is the same machinery with the initializer stored inside, so it can be used like a normal static:
static TABLE: LazyLock<Vec<u64>> =
LazyLock::new(|| expensive());Reach for LazyLock when the initializer is a self-contained closure, and OnceLock when the value comes from somewhere the static cannot see, such as a command-line argument set during startup.
Fill in three items. Settings and the BUILD_COUNT counter are given to you.
pub struct Settings {
pub name: String,
pub retries: u32,
}build_settings() is also given. It bumps BUILD_COUNT and returns Settings { name: "rustfinity", retries: 3 }. Do not change it: the tests use that counter to prove your initializer runs exactly once.
Declare a static OnceLock<Settings> for settings() to fill.
settingspub fn settings() -> &'static SettingsReturns the shared settings, building them with build_settings on the first call only. Every call, from every thread, must return a reference to the same value.
build_countpub fn build_count() -> usizeReads BUILD_COUNT. After any number of settings() calls from any number of threads, this must be 1.
TRIANGULARpub static TRIANGULAR: LazyLock<Vec<u64>>The first 32 triangular numbers, computed on first use: 0, 1, 3, 6, 10, 15, ..., where entry n is the sum of every integer from 0 to n.
assert_eq!(TRIANGULAR[..6], [0, 1, 3, 6, 10, 15]);
assert_eq!(TRIANGULAR.len(), 32);OnceLock::new() is a const fn, which is what lets it appear in a static.LazyLock<Vec<u64>> derefs to Vec<u64>, so TRIANGULAR[3] and TRIANGULAR.len() work without any extra unwrapping.static SETTINGS: OnceLock<Settings> = OnceLock::new();.get_or_init takes the initializer as a function, so SETTINGS.get_or_init(build_settings) is the whole body of settings. No parentheses after build_settings: you are passing the function, not calling it.build_count is one load(Ordering::Relaxed) on the counter.(0..32).map(|n: u64| n * (n + 1) / 2).collect().LazyLock::new wants a closure taking no arguments.Some values are expensive to build and should only be built if something actually asks for them: a parsed config, a compiled regex, a lookup table. A static cannot help, because a static initializer has to be a constant expression evaluated at compile time.
OnceLock<T> is the answer. It is a slot that starts empty and can be filled exactly once:
static SETTINGS: OnceLock<Settings> = OnceLock::new();
fn settings() -> &'static Settings {
SETTINGS.get_or_init(build_settings)
}get_or_init is where the concurrency lives. If ten threads call it at the same moment on an empty lock, exactly one runs build_settings. The other nine block until it finishes and then get a reference to that same value. The initializer never runs twice, and no caller ever sees a half-built value.
Because the value can never be replaced, handing out &'static T is sound, and callers need no lock at all to read it.
LazyLock<T> is the same machinery with the initializer stored inside, so it can be used like a normal static:
static TABLE: LazyLock<Vec<u64>> =
LazyLock::new(|| expensive());Reach for LazyLock when the initializer is a self-contained closure, and OnceLock when the value comes from somewhere the static cannot see, such as a command-line argument set during startup.
Fill in three items. Settings and the BUILD_COUNT counter are given to you.
pub struct Settings {
pub name: String,
pub retries: u32,
}build_settings() is also given. It bumps BUILD_COUNT and returns Settings { name: "rustfinity", retries: 3 }. Do not change it: the tests use that counter to prove your initializer runs exactly once.
Declare a static OnceLock<Settings> for settings() to fill.
settingspub fn settings() -> &'static SettingsReturns the shared settings, building them with build_settings on the first call only. Every call, from every thread, must return a reference to the same value.
build_countpub fn build_count() -> usizeReads BUILD_COUNT. After any number of settings() calls from any number of threads, this must be 1.
TRIANGULARpub static TRIANGULAR: LazyLock<Vec<u64>>The first 32 triangular numbers, computed on first use: 0, 1, 3, 6, 10, 15, ..., where entry n is the sum of every integer from 0 to n.
assert_eq!(TRIANGULAR[..6], [0, 1, 3, 6, 10, 15]);
assert_eq!(TRIANGULAR.len(), 32);OnceLock::new() is a const fn, which is what lets it appear in a static.LazyLock<Vec<u64>> derefs to Vec<u64>, so TRIANGULAR[3] and TRIANGULAR.len() work without any extra unwrapping.static SETTINGS: OnceLock<Settings> = OnceLock::new();.get_or_init takes the initializer as a function, so SETTINGS.get_or_init(build_settings) is the whole body of settings. No parentheses after build_settings: you are passing the function, not calling it.build_count is one load(Ordering::Relaxed) on the counter.(0..32).map(|n: u64| n * (n + 1) / 2).collect().LazyLock::new wants a closure taking no arguments.