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
85// Common trait delegates
86
87impl<T: Sync + ::core::fmt::Debug> ::core::fmt::Debug for Static<T> {
88    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
89        (**self).fmt(f)
90    }
91}
92
93impl<T: Sync + ::core::fmt::Display> ::core::fmt::Display for Static<T> {
94    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
95        (**self).fmt(f)
96    }
97}
98
99impl<T: Sync> Deref for Static<T> {
100    type Target = T;
101    #[inline]
102    fn deref(&self) -> &Self::Target {
103        if self.initialized.load(Ordering::Acquire) == INITIALIZED {
104            // SAFETY: We only access the static variable if the initialized
105            // state is `INITIALIZED`
106            unsafe { self.get_unchecked() }
107        } else {
108            self.deref_slow()
109        }
110    }
111}
112
113impl<T: Sync> Static<T> {
114    #[cold]
115    fn deref_slow(&self) -> &T {
116        struct PanicGuard<'a> {
117            initialized: &'a AtomicU8,
118        }
119        impl<'a> Drop for PanicGuard<'a> {
120            fn drop(&mut self) {
121                self.initialized.fetch_or(POISONED, Ordering::AcqRel);
122            }
123        }
124
125        // SAFETY: We only access the static variable if the initialized
126        // state is `INITIALIZED`, or if we are `UNINITIALIZED` and put the
127        // state into `INITIALIZED`.
128        unsafe {
129            match self.initialized.fetch_or(INITIALIZING, Ordering::AcqRel) {
130                UNINITIALIZED => {
131                    let panic_guard = PanicGuard {
132                        initialized: &self.initialized,
133                    };
134                    let value = (self.initializer)();
135                    core::mem::forget(panic_guard);
136                    ptr::write(self.storage.get() as _, value);
137                    self.initialized
138                        .fetch_or(FINISHED_INITIALIZING, Ordering::AcqRel);
139                    self.get_unchecked()
140                }
141                INITIALIZING => {
142                    panic!("Recursive or overlapping initialization of static variable");
143                }
144                INITIALIZED => self.get_unchecked(),
145                x if x & POISONED != 0 => panic!("Static variable has been poisoned"),
146                _ => panic!("Invalid state for static variable"),
147            }
148        }
149    }
150}
151
152impl<T: Sync> Drop for Static<T> {
153    fn drop(&mut self) {
154        // SAFETY: We only drop if the static is in the `INITIALIZED` state,
155        // which is can never leave unless going through this drop path. We
156        // leak in all other cases (though nothing should be written in any
157        // of those cases).
158        unsafe {
159            if INITIALIZED == self.initialized.fetch_or(DROPPING, Ordering::AcqRel) {
160                (UnsafeCell::raw_get(&self.storage) as *mut T).drop_in_place();
161                self.initialized.fetch_or(DROPPED, Ordering::AcqRel);
162            }
163        }
164    }
165}