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}