mulch/init_guard.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
/*
* Copyright 2022, Isaac Woods
* SPDX-License-Identifier: MPL-2.0
*/
use core::{
cell::UnsafeCell,
mem::MaybeUninit,
sync::atomic::{AtomicU8, Ordering},
};
/// A guard for when you want to store some data in a static, and explicitly initialize it at some point. This
/// is different from `spin::Once` in that you do not need to provide an initialization method every time you
/// access the object (it also uses `MaybeUninit` instead of `Option`). This will only release shared
/// references to the data inside, so if you want to mutate it from mutable threads, you'll need to use a type
/// like `Mutex` or `RwLock` within this.
pub struct InitGuard<T> {
/// The actual data. We can only assume that this holds initialized data if the state is
/// `STATE_INITIALIZED`.
data: UnsafeCell<MaybeUninit<T>>,
state: AtomicU8,
}
unsafe impl<T: Send + Sync> Sync for InitGuard<T> {}
unsafe impl<T: Send> Send for InitGuard<T> {}
const STATE_UNINIT: u8 = 0;
const STATE_INITIALIZING: u8 = 1;
const STATE_INITIALIZED: u8 = 2;
impl<T> InitGuard<T> {
pub const fn uninit() -> InitGuard<T> {
InitGuard { data: UnsafeCell::new(MaybeUninit::uninit()), state: AtomicU8::new(STATE_UNINIT) }
}
/// Initialize this `InitGuard`, allowing it to be read from in the future.
///
/// ### Panics
/// Panics if this `InitGuard` has already been initialized.
pub fn initialize(&self, value: T) {
match self.state.compare_exchange(STATE_UNINIT, STATE_INITIALIZING, Ordering::AcqRel, Ordering::Relaxed) {
Ok(STATE_UNINIT) => {
unsafe {
/*
* We make sure to initialize the entire data before marking ourselves as
* initialized. If we read from the data before doing this, we cause UB.
*/
(*self.data.get()).as_mut_ptr().write(value);
}
self.state.store(STATE_INITIALIZED, Ordering::Release);
}
Err(STATE_INITIALIZING) | Err(STATE_INITIALIZED) => panic!("InitGuard has already been initialized!"),
_ => panic!("InitGuard has invalid state"),
}
}
/// Get a reference to the data, if this guard has been initialized.
///
/// ### Panics
/// Panics if this guard hasn't been initialized yet. Use `try_get` if you want a fallible
/// variant.
#[track_caller]
pub fn get(&self) -> &T {
match self.state.load(Ordering::Acquire) {
/*
* Here, we create a reference to the data within the `MaybeUninit`. This causes UB if
* the data isn't really initialized.
*/
STATE_INITIALIZED => unsafe { (*self.data.get()).assume_init_ref() },
STATE_UNINIT | STATE_INITIALIZING => panic!("InitGuard has not been initialized!"),
_ => panic!("InitGuard has invalid state"),
}
}
/// Get a mutable reference to the data, if this guard has been initialized.
///
/// ### Panics
/// Panics if this guard hasn't been initialized yet. Use `try_get_mut` if you want a fallible
/// variant.
pub fn get_mut(&mut self) -> &mut T {
match self.state.load(Ordering::Acquire) {
/*
* Here, we create a reference to the data within the `MaybeUninit`. This causes UB if
* the data isn't really initialized.
*/
STATE_INITIALIZED => unsafe { (*self.data.get()).assume_init_mut() },
STATE_UNINIT | STATE_INITIALIZING => panic!("InitGuard has not been initialized!"),
_ => panic!("InitGuard has invalid state"),
}
}
/// Get a reference to the data, if this guard has been initialized. Returns `None` if it has
/// not yet been initialized, or is currently being initialized.
pub fn try_get(&self) -> Option<&T> {
match self.state.load(Ordering::Acquire) {
/*
* Here, we create a reference to the data within the `MaybeUninit`. This causes UB if
* the data isn't really initialized.
*/
STATE_INITIALIZED => Some(unsafe { (*self.data.get()).assume_init_ref() }),
STATE_UNINIT | STATE_INITIALIZING => None,
_ => panic!("InitGuard has invalid state"),
}
}
/// Get a mutable reference to the data, if this guard has been initialized. Returns `None` if it has
/// not yet been initialized, or is currently being initialized.
pub fn try_get_mut(&mut self) -> Option<&mut T> {
match self.state.load(Ordering::Acquire) {
/*
* Here, we create a reference to the data within the `MaybeUninit`. This causes UB if
* the data isn't really initialized.
*/
STATE_INITIALIZED => Some(unsafe { (*self.data.get()).assume_init_mut() }),
STATE_UNINIT | STATE_INITIALIZING => None,
_ => panic!("InitGuard has invalid state"),
}
}
}