Skip to main content

lazy_init/
lib.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//! Explicit initialization for objects in static storage.
6//!
7//! [`LazyInit`] provides explicit, one-time initialization for values in
8//! `static` storage that cannot be constructed in a `const` context. Accesses
9//! are validated according to the specified [`Policy`] strategy.
10
11#![no_std]
12
13mod check;
14
15pub use check::{AtomicCheck, BasicCheck, CheckedPolicy, Lifecycle, NoCheck, Policy};
16
17use core::cell::UnsafeCell;
18use core::convert::Infallible;
19use core::marker::PhantomData;
20use core::mem::MaybeUninit;
21use core::ops;
22use core::pin::Pin;
23
24use pin_init::PinInit;
25
26/// A cell providing explicit, one-time initialization for a static value of
27/// type `T`.
28///
29/// `LazyInit` allows storing types in static memory that require runtime
30/// initialization. Accesses are validated according to the specified `Policy`
31/// strategy.
32pub struct LazyInit<T, Check: Policy = BasicCheck> {
33    value: UnsafeCell<MaybeUninit<T>>,
34    check: Check::State,
35    _check: PhantomData<Check>,
36}
37
38impl<T, Check: Policy> LazyInit<T, Check> {
39    /// Creates an uninitialized instance.
40    pub const fn uninit() -> Self {
41        Self {
42            value: UnsafeCell::new(MaybeUninit::uninit()),
43            check: Check::UNINIT_STATE,
44            _check: PhantomData,
45        }
46    }
47
48    /// Moves the given value into the wrapped storage.
49    ///
50    /// # Safety
51    ///
52    /// The caller must ensure that initialization is serialized with respect to
53    /// any other access to this instance (i.e., that `init` does not race with
54    /// any concurrent reads or other initialization calls).
55    ///
56    /// # Panics
57    ///
58    /// Panics if the instance has already been initialized or is currently
59    /// initializing.
60    pub unsafe fn init(&'static self, value: T) {
61        let _ = Check::init_with(&self.check, || -> Result<(), Infallible> {
62            unsafe {
63                (*self.value.get()).write(value);
64            }
65            Ok(())
66        });
67    }
68
69    /// Explicitly constructs the wrapped value in-place using a [`PinInit`]
70    /// initializer.
71    ///
72    /// # Safety
73    ///
74    /// The caller must ensure that initialization is serialized with respect to
75    /// any other access to this instance (i.e., that `init_pin` does not race
76    /// with any concurrent reads or other initialization calls).
77    ///
78    /// # Panics
79    ///
80    /// Panics if the instance has already been initialized or is currently
81    /// initializing.
82    pub unsafe fn init_pin<E>(self: Pin<&'static Self>, init: impl PinInit<T, E>) -> Result<(), E> {
83        let slot = unsafe { (*self.value.get()).as_mut_ptr() };
84        // Safety: `slot` is pinned since `self` is.
85        Check::init_with(&self.check, || unsafe { init.__pinned_init(slot) })
86    }
87
88    /// Returns a reference to the wrapped value without checking
89    /// initialization state.
90    ///
91    /// # Safety
92    ///
93    /// The caller must ensure that the instance has indeed been initialized.
94    #[inline(always)]
95    pub const unsafe fn get_unchecked(&self) -> &T {
96        unsafe { (*self.value.get()).assume_init_ref() }
97    }
98}
99
100impl<T, Check: CheckedPolicy> LazyInit<T, Check> {
101    /// Returns a reference to the wrapped value.
102    ///
103    /// Asserts that initialization has already occurred.
104    ///
105    /// # Panics
106    ///
107    /// Panics if the instance has not been initialized.
108    #[inline(always)]
109    pub fn get(&self) -> &T {
110        Check::assert_initialized(&self.check);
111        // Safety: state just checked as initialized.
112        unsafe { self.get_unchecked() }
113    }
114}
115
116impl<T, Check: CheckedPolicy> ops::Deref for LazyInit<T, Check> {
117    type Target = T;
118
119    #[inline(always)]
120    fn deref(&self) -> &Self::Target {
121        self.get()
122    }
123}
124
125// Safety: If T does not have any thread-affinity, then neither does LazyInit.
126unsafe impl<T: Send, Check: Policy> Send for LazyInit<T, Check> where Check::State: Send {}
127
128// Safety: The caller of `init` / `init_pin` guarantees that initialization is
129// serialized with respect to all other accesses. After initialization, the
130// instance is immutable, making concurrent reads data-race free.
131unsafe impl<T: Sync, Check: Policy> Sync for LazyInit<T, Check> {}
132
133#[cfg(test)]
134mod tests {
135    use pin_init::{pin_data, pin_init};
136
137    use super::*;
138
139    #[test]
140    fn basic_check() {
141        static LAZY: LazyInit<i32, BasicCheck> = LazyInit::uninit();
142        unsafe {
143            LAZY.init(42);
144        }
145        assert_eq!(*LAZY.get(), 42);
146        assert_eq!(*LAZY, 42);
147        assert_eq!(unsafe { *LAZY.get_unchecked() }, 42);
148    }
149
150    #[test]
151    fn no_check() {
152        static LAZY: LazyInit<i32, NoCheck> = LazyInit::uninit();
153        unsafe {
154            LAZY.init(42);
155        }
156        assert_eq!(unsafe { *LAZY.get_unchecked() }, 42);
157    }
158
159    #[test]
160    #[should_panic(expected = "LazyInit: accessed before initialization")]
161    fn basic_uninit_get_panics() {
162        static LAZY: LazyInit<i32, BasicCheck> = LazyInit::uninit();
163        let _ = LAZY.get();
164    }
165
166    #[test]
167    #[should_panic(expected = "LazyInit: already initialized")]
168    fn basic_double_init_panics() {
169        static LAZY: LazyInit<i32, BasicCheck> = LazyInit::uninit();
170        unsafe {
171            LAZY.init(1);
172            LAZY.init(2);
173        }
174    }
175
176    #[test]
177    fn atomic_check() {
178        static LAZY: LazyInit<&'static str, AtomicCheck> = LazyInit::uninit();
179        unsafe {
180            LAZY.init("hello world");
181        }
182        assert_eq!(*LAZY.get(), "hello world");
183    }
184
185    #[test]
186    #[should_panic(expected = "LazyInit: accessed before initialization")]
187    fn atomic_uninit_get_panics() {
188        static LAZY: LazyInit<i32, AtomicCheck> = LazyInit::uninit();
189        let _ = LAZY.get();
190    }
191
192    #[test]
193    #[should_panic(expected = "LazyInit: already initialized")]
194    fn atomic_double_init_panics() {
195        static LAZY: LazyInit<i32, AtomicCheck> = LazyInit::uninit();
196        unsafe {
197            LAZY.init(1);
198            LAZY.init(2);
199        }
200    }
201
202    #[test]
203    fn init_pin() {
204        #[pin_data]
205        struct PinnedStruct {
206            val: u32,
207        }
208
209        static LAZY: LazyInit<PinnedStruct, BasicCheck> = LazyInit::uninit();
210        let pinned = Pin::static_ref(&LAZY);
211        unsafe {
212            pinned.init_pin(pin_init!(PinnedStruct { val: 123 })).unwrap();
213        }
214        assert_eq!(pinned.get().val, 123);
215    }
216}