LazyCell
と LazyLock
は遅延初期化を提供する構造体で、Rust 1.80.0 で安定版となった。
LazyLock
はスレッドセーフであり、static 変数を遅延初期化できる。
LazyCell
LazyCell
は初回アクセス時に指定された関数 F
で値を初期化し、それ以降は初期化済みの値を利用できる。
以下のような構造体として提供されている。
pub struct LazyCell<T, F = fn() -> T> { state: UnsafeCell<State<T, F>>, }
単純に 3
で遅延初期化する例としては以下のようになる。
use std::cell::LazyCell; let lazy: LazyCell<i32> = LazyCell::new(|| 3); println!("{}", *lazy);
Deref
を以下のように実装しているため、*lazy
の参照外しで値が初期化される。
impl<T, F: FnOnce() -> T> Deref for LazyCell<T, F> { type Target = T; #[inline] fn deref(&self) -> &T { LazyCell::force(self) } }
実際の初期化処理は、LazyCell::force()
で、以下のように状態に応じて初期化処理の実行制御が行われる。
pub fn force(this: &LazyCell<T, F>) -> &T { let state = unsafe { &*this.state.get() }; match state { State::Init(data) => data, State::Uninit(_) => unsafe { LazyCell::really_init(this) }, State::Poisoned => panic!("LazyCell has previously been poisoned"), } }
Rust 1.70.0 で安定化された std::cell::OnceCell
や std::sync::OnceLock
と似ているが、OnceCell
では get_or_init()
で都度初期化関数を指定する必要がある。
use std::cell::OnceCell; let cell = OnceCell::new(); let value: &String = cell.get_or_init(|| { "Hello, World!".to_string() });
OnceCell
は値の書き込みが1回しかできない同期プリミティブ。
LazyLock
LazyLock
は Sync
トレイトを実装しているため、スレッドセーフに利用できる。
以下のように static 変数を遅延初期化することが可能となる。
use std::sync::LazyLock; use std::time::Instant; static LAZY_TIME: LazyLock<Instant> = LazyLock::new(Instant::now); fn main() { let start = Instant::now(); std::thread::scope(|s| { s.spawn(|| { println!("Thread lazy time is {:?}", LAZY_TIME.duration_since(start)); }); println!("Main lazy time is {:?}", LAZY_TIME.duration_since(start)); }); }
LazyLock
値への初回アクセス時にスレッドセーフに初期化されるため、それぞれのスレッドで同じ経過時間が表示される。
LazyCell
は !Sync
トレイトを実装するため、上記の使い方はできない。