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}