Skip to main content

ctor/
statics.rs

1//! Support for static variables that are initialized at startup time.
2#![allow(unsafe_code)]
3
4use core::cell::UnsafeCell;
5use core::mem::MaybeUninit;
6use core::ops::Deref;
7use core::ptr;
8use core::sync::atomic::{AtomicU8, Ordering};
9
10/// A static variable intended to be initialized before `main`.
11///
12/// ## Expected usage
13///
14/// This type is designed for the "startup is single-threaded" model: it is
15/// expected that no other threads access the value until initialization has
16/// completed. After that point, the value is treated as initialized and
17/// immutable.
18///
19/// ## Concurrency
20///
21/// If the value is accessed concurrently while it is still being
22/// initialized (for example, from another thread during startup, including
23/// when used from a dynamic library), this is not undefined behavior, but
24/// this implementation does not wait for initialization to complete: it
25/// will panic instead.
26///
27/// If you need concurrent first access to be handled by blocking/spinning
28/// rather than panicking, use [`std::sync::OnceLock`] or
29/// [`std::sync::LazyLock`] (when `std` is available).
30///
31/// ## Panics / poisoning
32///
33/// If the initializer panics, the static becomes permanently poisoned: all
34/// subsequent accesses will panic, and it cannot be reset.
35pub struct Static<T: Sync> {
36    storage: UnsafeCell<MaybeUninit<T>>,
37    initializer: fn() -> T,
38    initialized: AtomicU8,
39}
40
41const UNINITIALIZED: u8 = 0;
42const INITIALIZING: u8 = 1;
43const FINISHED_INITIALIZING: u8 = 2;
44const INITIALIZED: u8 = 3;
45const DROPPING: u8 = 4;
46const POISONED: u8 = 0x10;
47const DROPPED: u8 = 0xff;
48
49/// SAFETY: This is safe because the static variable is either initialized
50/// and read-only, poisoned and panicing, or initializing (and will panic if
51/// multiple threads try to initialize it at the same time).
52unsafe impl<T: Sync> Sync for Static<T> {}
53
54impl<T: Sync> Static<T> {
55    /// Create a new ctor-initialized static variable.
56    ///
57    /// # Safety
58    ///
59    /// See the documentation for `Static` for more information.
60    #[doc(hidden)]
61    pub const unsafe fn new(initializer: fn() -> T) -> Self {
62        Self {
63            storage: UnsafeCell::new(MaybeUninit::uninit()),
64            initializer,
65            initialized: AtomicU8::new(UNINITIALIZED),
66        }
67    }
68
69    /// Get the underlying value of the static variable without checking the
70    /// initialized state.
71    ///
72    /// # Safety
73    ///
74    /// This must only be called if the initialized state is `INITIALIZED`.
75    #[inline(always)]
76    unsafe fn get_unchecked(&self) -> &T {
77        unsafe {
78            (UnsafeCell::raw_get(&self.storage) as *const T)
79                .as_ref()
80                .unwrap_unchecked()
81        }
82    }
83}
84
85impl<T: Sync> Deref for Static<T> {
86    type Target = T;
87    #[inline]
88    fn deref(&self) -> &Self::Target {
89        struct PanicGuard<'a> {
90            initialized: &'a AtomicU8,
91        }
92        impl<'a> Drop for PanicGuard<'a> {
93            fn drop(&mut self) {
94                self.initialized.fetch_or(POISONED, Ordering::AcqRel);
95            }
96        }
97
98        // SAFETY: We only access the static variable if the initialized
99        // state is `INITIALIZED`, or if we are `UNINITIALIZED` and put the
100        // state into `INITIALIZED`.
101        unsafe {
102            match self.initialized.fetch_or(INITIALIZING, Ordering::AcqRel) {
103                UNINITIALIZED => {
104                    let panic_guard = PanicGuard {
105                        initialized: &self.initialized,
106                    };
107                    let value = (self.initializer)();
108                    core::mem::forget(panic_guard);
109                    ptr::write(self.storage.get() as _, value);
110                    self.initialized
111                        .fetch_or(FINISHED_INITIALIZING, Ordering::AcqRel);
112                    self.get_unchecked()
113                }
114                INITIALIZING => {
115                    panic!("Recursive or overlapping initialization of static variable");
116                }
117                INITIALIZED => self.get_unchecked(),
118                x if x & POISONED != 0 => panic!("Static variable has been poisoned"),
119                _ => panic!("Invalid state for static variable"),
120            }
121        }
122    }
123}
124
125impl<T: Sync> Drop for Static<T> {
126    fn drop(&mut self) {
127        // SAFETY: We only drop if the static is in the `INITIALIZED` state,
128        // which is can never leave unless going through this drop path. We
129        // leak in all other cases (though nothing should be written in any
130        // of those cases).
131        unsafe {
132            if INITIALIZED == self.initialized.fetch_or(DROPPING, Ordering::AcqRel) {
133                (UnsafeCell::raw_get(&self.storage) as *mut T).drop_in_place();
134                self.initialized.fetch_or(DROPPED, Ordering::AcqRel);
135            }
136        }
137    }
138}