Skip to main content

lazy_init/
check.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// TODO(https://github.com/rust-lang/rust/issues/143874): Replace constants
6// with the return values of a const trait method instead.
7#![allow(clippy::declare_interior_mutable_const)]
8
9use core::cell::UnsafeCell;
10use core::sync::atomic::{AtomicU8, Ordering};
11
12/// Lifecycle states of a [`crate::LazyInit`]-wrapped object.
13#[derive(Clone, Copy, Eq, Debug, PartialEq)]
14#[repr(u8)]
15pub enum Lifecycle {
16    Uninitialized = 0,
17    Initializing = 1,
18    Initialized = 2,
19}
20
21impl Lifecycle {
22    pub const fn from_u8(val: u8) -> Self {
23        match val {
24            0 => Lifecycle::Uninitialized,
25            1 => Lifecycle::Initializing,
26            2 => Lifecycle::Initialized,
27            _ => unreachable!(),
28        }
29    }
30}
31
32/// Lifecycle and storage policy for initialization state.
33pub trait Policy {
34    /// Associated storage for state tracking.
35    type State;
36
37    /// An instance of state in the uninitialized condition.
38    const UNINIT_STATE: Self::State;
39
40    /// Transitions `state` from `from` to `to`, panicking if the current state
41    /// does not match `from`.
42    fn transition(state: &Self::State, from: Lifecycle, to: Lifecycle);
43
44    /// Executes `init_fn` to construct the wrapped value within an
45    /// initialization transition.
46    ///
47    /// Transitions to the initialized state if `init_fn` succeeds with `Ok`, or
48    /// reverts to the uninitialized state if it returns `Err`.
49    ///
50    /// # Panics
51    ///
52    /// Panics if the instance is not currently uninitialized.
53    #[inline]
54    fn init_with<R, E>(
55        state: &Self::State,
56        init_fn: impl FnOnce() -> Result<R, E>,
57    ) -> Result<R, E> {
58        Self::transition(state, Lifecycle::Uninitialized, Lifecycle::Initializing);
59        let result = init_fn();
60        let end_state =
61            if result.is_ok() { Lifecycle::Initialized } else { Lifecycle::Uninitialized };
62        Self::transition(state, Lifecycle::Initializing, end_state);
63        result
64    }
65}
66
67/// An initialization policy that actively validates initialization state.
68pub trait CheckedPolicy: Policy {
69    /// Asserts that the instance has been initialized.
70    ///
71    /// This method is called on the hot path by [`crate::LazyInit::get`] before
72    /// reading the wrapped value. Implementations should always inline and
73    /// reduce to a single load, compare, and conditional branch to an
74    /// out-of-line, cold panic path.
75    ///
76    /// # Panics
77    ///
78    /// Panics if the instance has not been initialized.
79    fn assert_initialized(state: &Self::State);
80}
81
82// Out-of-line cold panic path per `assert_initialized()`.
83#[cold]
84#[inline(never)]
85fn panic_uninitialized() -> ! {
86    panic!("LazyInit: accessed before initialization");
87}
88
89fn panic_unexpected_state(actual: Lifecycle) -> ! {
90    match actual {
91        Lifecycle::Initializing => {
92            panic!("LazyInit: concurrent or reentrant initialization in progress")
93        }
94        Lifecycle::Initialized => panic!("LazyInit: already initialized"),
95        _ => panic!("LazyInit: invalid state transition"),
96    }
97}
98
99pub struct NoCheck;
100
101impl Policy for NoCheck {
102    type State = ();
103
104    const UNINIT_STATE: Self::State = ();
105
106    fn transition(_state: &Self::State, _from: Lifecycle, _to: Lifecycle) {}
107}
108
109/// Check strategy that defers synchronization to the caller.
110pub struct BasicCheck;
111
112impl Policy for BasicCheck {
113    type State = UnsafeCell<Lifecycle>;
114
115    const UNINIT_STATE: Self::State = UnsafeCell::new(Lifecycle::Uninitialized);
116
117    fn transition(state: &Self::State, from: Lifecycle, to: Lifecycle) {
118        // Safety: synchronization is deferred to the caller.
119        let current = unsafe { *state.get() };
120        if current != from {
121            panic_unexpected_state(current);
122        }
123        // Safety: synchronization is deferred to the caller.
124        unsafe { state.get().write(to) };
125    }
126}
127
128impl CheckedPolicy for BasicCheck {
129    // Implemented per the documented criteria of the trait method.
130    #[inline(always)]
131    fn assert_initialized(state: &Self::State) {
132        // Safety: synchronization is deferred to the caller.
133        if unsafe { *state.get() } != Lifecycle::Initialized {
134            panic_uninitialized();
135        }
136    }
137}
138
139/// Check strategy that accesses and updates state atomically.
140pub struct AtomicCheck;
141
142impl Policy for AtomicCheck {
143    type State = AtomicU8;
144
145    const UNINIT_STATE: Self::State = AtomicU8::new(Lifecycle::Uninitialized as u8);
146
147    fn transition(state: &Self::State, from: Lifecycle, to: Lifecycle) {
148        let order = match to {
149            // Acquire: Prevents memory writes during initialization from being
150            // reordered before claiming "initializing" exclusivity, and also
151            // synchronizes with any prior failed initialization that reverted
152            // state back to uninitialized.
153            Lifecycle::Initializing => Ordering::Acquire,
154
155            // Release: Publishes all memory writes performed during
156            // initialization so that readers observing `Initialized` with
157            // `Acquire` see the fully constructed value.
158            Lifecycle::Initialized => Ordering::Release,
159
160            // Release: Ensures any cleanup or drop writes from a failed
161            // initialization are committed before resetting state to
162            // `Uninitialized`.
163            Lifecycle::Uninitialized => Ordering::Release,
164        };
165        if let Err(actual) = state.compare_exchange(from as u8, to as u8, order, Ordering::Relaxed)
166        {
167            panic_unexpected_state(Lifecycle::from_u8(actual));
168        }
169    }
170}
171
172impl CheckedPolicy for AtomicCheck {
173    // Implemented per the documented criteria of the trait method.
174    #[inline(always)]
175    fn assert_initialized(state: &Self::State) {
176        // Acquire: Synchronizes with the `Release` write when transitioning to
177        // `Initialized`, ensuring all writes constructing the wrapped value
178        // are visible before this thread reads the payload.
179        if state.load(Ordering::Acquire) != Lifecycle::Initialized as u8 {
180            panic_uninitialized();
181        }
182    }
183}