freezebox/maybebox.rs
1//! This is the MaybeBox implementation.
2
3use alloc::boxed::Box;
4use core::any::type_name;
5use core::marker::PhantomData;
6use core::ops::Deref;
7use core::ptr::null_mut;
8use core::sync::atomic::{AtomicPtr, Ordering};
9use core::{mem, ptr};
10
11/// `MaybeBox` is a lazy-initialized container.
12///
13/// A `MaybeBox<T>` can have two possible states:
14/// * uninitialized: it does not contain a `T`, and [`get`] will return `None`.
15/// * initialized: it does contain a `T`, and [`get`] will return `Some(&T)`.
16///
17/// To upgrade a `MaybeBox` to the initialized state, call `lazy_init`.
18/// `lazy_init` does not require a mutable reference, making `MaybeBox`
19/// suitable for sharing objects first and initializing them later.
20///
21/// `MaybeBox` does not implement `Deref`; to access the contents call [`get`].
22///
23/// # Panics
24///
25/// Attempting to `lazy_init` more than once will cause a panic.
26///
27/// [`get`]: [`MaybeBox::get`]
28///
29/// # Examples
30/// ```
31/// # use freezebox::MaybeBox;
32/// # let some_runtime_config = true;
33/// let x = MaybeBox::<String>::default();
34/// if some_runtime_config {
35/// x.lazy_init(String::from("hello"));
36/// }
37/// if let Some(val) = x.get() {
38/// println!("{}", val);
39/// }
40/// ```
41///
42pub struct MaybeBox<T> {
43 inner: AtomicPtr<T>,
44 phantom: PhantomData<T>,
45}
46
47impl<T> MaybeBox<T> {
48 /// Create a new `MaybeBox` with optional initialization.
49 ///
50 /// A pre-inititialized `MaybeBox<T>` may not seem very useful, but it
51 /// can be convenient when interacting with structs or interfaces that
52 /// require a `MaybeBox`, e.g. unit tests.
53 ///
54 /// To always create an uninitialized `MaybeBox`, use
55 /// `MaybeBox::default()`.
56 ///
57 pub fn new(val: Option<T>) -> Self {
58 match val {
59 None => Self::default(),
60 Some(v) => {
61 let fb = Self::default();
62 fb.lazy_init(v);
63 fb
64 }
65 }
66 }
67
68 /// Create a new `MaybeBox` in `const` context
69 ///
70 /// This is the same as `MaybeBox::default` except that it works in
71 /// const context, which is desirable for global `static` singleton objects.
72 ///
73 /// # Examples
74 /// ```
75 /// # use freezebox::MaybeBox;
76 /// static X: MaybeBox<String> = MaybeBox::const_default();
77 /// X.lazy_init("hello".to_string());
78 /// assert_eq!(X.get_as_deref(), Some("hello"));
79 /// ```
80 pub const fn const_default() -> Self {
81 Self {
82 inner: AtomicPtr::new(null_mut()),
83 phantom: PhantomData,
84 }
85 }
86
87 /// Initialize a `MaybeBox`.
88 ///
89 /// The new value will be stored on the heap.
90 ///
91 /// # Panics
92 ///
93 /// `lazy_init` will panic if the `FreezeBox` is already initialized.
94 /// If it panics, the input value will be dropped.
95 ///
96 pub fn lazy_init(&self, val: T) {
97 let ptr = Box::into_raw(Box::new(val));
98
99 // Attempt to atomically swap from nullptr to `ptr`.
100 //
101 // Reasoning about the atomic ordering:
102 // On the success side, we don't care about the load ordering,
103 // only the store ordering, which must be `Release` or stronger.
104 // This is because if we succeed, the previous value was null,
105 // which is the state of a newly-created AtomicBox. So it's not
106 // possible for us to race with another thread storing the null
107 // pointer.
108 //
109 // On the failure side, we want to detect a race to double init,
110 // so we want the load to be `Acquire` or stronger.
111 //
112 // Because the success ordering must be equal or stronger to the
113 // failure ordering, we need to upgrade the success ordering to
114 // `AcqRel`.
115 //
116 // If this succeeds, the MaybeBox is now initialized.
117 if self
118 .inner
119 .compare_exchange(ptr::null_mut(), ptr, Ordering::AcqRel, Ordering::Acquire)
120 .is_err()
121 {
122 // The compare_exchange failed, meaning a double-init was
123 // attempted and we should panic.
124 //
125 // Before we do, retake ownership of the new pointer so that
126 // we don't leak its memory.
127 //
128 // SAFETY: `ptr` was just created above using `Box::into_raw`.
129 // Because compare_exchange failed, we know that it is still
130 // the unique owner of the input value. So we can reclaim
131 // ownership here and drop the result.
132
133 let _val = unsafe { Box::<T>::from_raw(ptr) };
134
135 panic!(
136 "lazy_init on already-initialized MaybeBox<{}>",
137 type_name::<T>()
138 );
139 }
140 }
141
142 /// Try to get a reference to the data in the `MaybeBox`.
143 ///
144 /// If the `MaybeBox` is initialized, this will return `Some(&T)`;
145 /// otherwise it will return None.
146 pub fn get(&self) -> Option<&T> {
147 let ptr = self.inner.load(Ordering::Acquire);
148 unsafe { ptr.as_ref() }
149 }
150
151 /// Test whether a `MaybeBox` is initialized.
152 pub fn is_initialized(&self) -> bool {
153 let ptr = self.inner.load(Ordering::Acquire);
154 !ptr.is_null()
155 }
156
157 /// Consume the `MaybeBox` and return its contents.
158 pub fn into_inner(self) -> Option<T> {
159 let ptr = self.inner.load(Ordering::Acquire);
160 // Prevent Drop::drop() from being called on the MaybeBox
161 // because we are transferring ownership elsewhere.
162 mem::forget(self);
163 if ptr.is_null() {
164 return None;
165 }
166
167 // SAFETY: because we are consuming self, we must have sole ownership
168 // of the MaybeBox contents. `lazy_init` created `ptr` from an
169 // owning `Box<T>`, so it's safe for us to recreate that Box and drop
170 // it.
171
172 let tmp_box = unsafe { Box::from_raw(ptr) };
173 Some(*tmp_box)
174 }
175}
176
177impl<T: Deref> MaybeBox<T> {
178 /// Try to `Deref` the contents of the the `MaybeBox`.
179 ///
180 /// This is helpful when you want the `Deref` form of the
181 /// data in the `MaybeBox`. For example, when called on a
182 /// `MaybeBox<String>`, this will return `Option<&str>`.
183 ///
184 /// If the `MaybeBox` is uninitialized, this will return None.
185 pub fn get_as_deref(&self) -> Option<&T::Target> {
186 let ptr = self.inner.load(Ordering::Acquire);
187 match unsafe { ptr.as_ref() } {
188 Some(t) => Some(t.deref()),
189 None => None,
190 }
191 }
192}
193
194impl<T> Default for MaybeBox<T> {
195 fn default() -> Self {
196 Self {
197 inner: AtomicPtr::default(),
198 phantom: PhantomData,
199 }
200 }
201}
202
203impl<T> Drop for MaybeBox<T> {
204 fn drop(&mut self) {
205 // We have exclusive access to the container, so this doesn't need
206 // to be atomic.
207 let inner = self.inner.get_mut();
208
209 if !inner.is_null() {
210 // We own an inner object. Re-hydrate into a Box<T> so that
211 // T's destructor may run.
212 //
213 // SAFETY: We have exclusive access to the inner value, so we can
214 // safely drop the contents. We could also reset the pointer, but
215 // since this data structure is being dropped, this is the last
216 // time that pointer will be seen; so there's no point.
217
218 let _owned = unsafe { Box::<T>::from_raw(*inner) };
219 // _owned will drop here.
220 }
221 }
222}
223
224/// Must fail to compile because MaybeBox<Rc> must not be Send.
225/// ```compile_fail
226/// use freezebox::MaybeBox;
227/// use std::rc::Rc;
228///
229/// fn require_send<T: Send>(_t: &T) {}
230///
231/// let x = MaybeBox::<Rc<u32>>::new();
232/// require_send(&x); // <- must fail to compile.
233/// ```
234///
235/// Must fail to compile because MaybeBox<Cell> must not be Sync.
236/// ```compile_fail
237/// use freezebox::MaybeBox;
238/// use std::cell::Cell;
239///
240/// fn require_sync<T: Sync>(_t: &T) {}
241///
242/// let x = MaybeBox::<Cell<u32>>::new();
243/// require_sync(&x); // must fail to compile.
244/// ```
245struct _Unused; // Only exists to get the compile-fail doctest
246
247#[cfg(test)]
248mod tests {
249 use super::MaybeBox;
250 use alloc::string::String;
251 use alloc::string::ToString;
252 use alloc::sync::Arc;
253
254 #[test]
255 fn freezebox_test() {
256 // Arc is used to check whether drop occurred.
257 let x = Arc::new("hello".to_string());
258 let y: MaybeBox<Arc<String>> = MaybeBox::default();
259 assert!(!y.is_initialized());
260 assert!(y.get().is_none());
261 y.lazy_init(x.clone());
262 assert!(y.is_initialized());
263 assert_eq!(**y.get().unwrap(), "hello");
264
265 // Verify that dropping the MaybeBox caused its inner value to be dropped too.
266 assert_eq!(Arc::strong_count(&x), 2);
267 drop(y);
268 assert_eq!(Arc::strong_count(&x), 1);
269 }
270
271 #[test]
272 #[should_panic]
273 fn panic_double_init() {
274 let x = MaybeBox::<String>::default();
275 x.lazy_init("first".to_string());
276 x.lazy_init("second".to_string());
277 }
278
279 #[test]
280 fn consume_test() {
281 let x = MaybeBox::<String>::default();
282 x.lazy_init("hello".to_string());
283 let x2: Option<String> = x.into_inner();
284 assert_eq!(x2, Some("hello".to_string()));
285
286 let y = MaybeBox::<String>::default();
287 let y2: Option<String> = y.into_inner();
288 assert_eq!(y2, None);
289 }
290
291 #[test]
292 fn const_test() {
293 static X: MaybeBox<String> = MaybeBox::const_default();
294 X.lazy_init("hello".to_string());
295 assert_eq!(X.get().unwrap(), "hello");
296 }
297}