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}