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 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
//! # Rust FreezeBox: a deref'able lazy-initialized container. //! //! `FreezeBox<T>` is a container that can have two possible states: //! * uninitialized: deref is not allowed. //! * initialized: deref to a `&T` is possible. //! //! To upgrade a `FreezeBox` to the initialized state, call `lazy_init`. //! `lazy_init` does not require a mutable reference, making `FreezeBox` //! suitable for sharing objects first and initializing them later. //! //! Attempting to `lazy_init` more than once, or deref while uninitialized //! will cause a panic. //! //! # Examples //! //! This example creates a shared data structure, then circles back to //! initialize one member. //! //! ``` //! use freezebox::FreezeBox; //! use std::sync::Arc; //! //! #[derive(Default)] //! struct Resources { //! name: FreezeBox<String> //! } //! //! let resources = Arc::new(Resources::default()); //! let res2 = resources.clone(); //! //! let func = move || { //! assert_eq!(*res2.name, "Hello!"); //! }; //! //! resources.name.lazy_init("Hello!".to_string()); //! func(); //! ``` use std::any::type_name; use std::ops::Deref; use std::ptr::null_mut; use std::sync::atomic::{AtomicPtr, Ordering}; /// `FreezeBox` is a deref'able lazy-initialized container. /// /// A `FreezeBox<T>` can have two possible states: /// * uninitialized: deref is not allowed. /// * initialized: deref to a `&T` is possible. /// /// To upgrade a `FreezeBox` to the initialized state, call `lazy_init`. /// `lazy_init` does not require a mutable reference, making `FreezeBox` /// suitable for sharing objects first and initializing them later. /// /// Attempting to `lazy_init` more than once, or deref while uninitialized /// will cause a panic. pub struct FreezeBox<T> { inner: AtomicPtr<T>, } impl<T> FreezeBox<T> { /// Create an uninitialized `FreezeBox`. pub fn new() -> Self { Self::default() } /// Initialize a `FreezeBox`. /// /// `lazy_init` will panic if the `FreezeBox` is already initialized. pub fn lazy_init(&self, val: T) { let ptr = Box::into_raw(Box::new(val)); let prev = self.inner.swap(ptr, Ordering::Release); if prev != null_mut() { // Note we will leak the value in prev. panic!( "lazy_init on already-initialized FreezeBox<{}>", type_name::<T>() ); } } } impl<T> Deref for FreezeBox<T> { type Target = T; fn deref(&self) -> &Self::Target { let inner = self.inner.load(Ordering::Acquire); let inner_ref = unsafe { inner.as_ref() }; inner_ref.unwrap_or_else(|| { panic!( "attempted to deref uninitialized FreezeBox<{}>", type_name::<T>(), ) }) } } impl<T> Default for FreezeBox<T> { fn default() -> Self { Self { inner: AtomicPtr::new(null_mut()), } } } impl<T> Drop for FreezeBox<T> { fn drop(&mut self) { // We have exclusive access to the container, so this doesn't need // to be atomic. let inner = self.inner.get_mut(); if *inner != null_mut() { // We own an inner object. Re-hydrate into a Box<T> so that // T's destructor may run. let _owned = unsafe { Box::<T>::from_raw(*inner) }; // _owned will drop here. } } } #[cfg(test)] mod tests { use super::FreezeBox; use std::sync::Arc; #[test] fn freezebox_test() { // Arc is used to check whether drop occurred. let x = Arc::new("hello".to_string()); let y: FreezeBox<Arc<String>> = FreezeBox::new(); y.lazy_init(x.clone()); // explicit deref once for FreezeBox and once for Arc. assert_eq!(**y, "hello"); // implicit deref sees through both layers. assert_eq!(&y[2..], "llo"); // Verify that dropping the FreezeBox caused its inner value to be dropped too. assert_eq!(Arc::strong_count(&x), 2); drop(y); assert_eq!(Arc::strong_count(&x), 1); } #[test] #[should_panic] #[cfg_attr(miri, ignore)] // Miri doesn't understand should_panic fn freezebox_panic_test() { let x = FreezeBox::<String>::new(); // dot-operator implicitly deref's the FreezeBox. let _y = x.len(); } }