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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
//! # 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.
//!
//! FreezeBox is compatible with `no_std` projects (no feature flags needed).
//! It may be used in any environment with a memory allocator.
//!
//! FreezeBox uses unsafe code internally. To ensure soundness, the unit
//! tests pass under Miri, and the unsafe code is simple and easy to
//! understand.
//!
//! The minimum supported Rust version is 1.48.
//!
//! # Examples
//!
//! This example creates a shared data structure, then circles back to
//! initialize one member.
//!
//! ```
//! use freezebox::FreezeBox;
//! use std::sync::Arc;
//!
//! /// A data structure that we will initialize lazily.
//! #[derive(Default)]
//! struct Resources {
//!     name: FreezeBox<String>
//! }
//!
//! // Create an instance of the `Resources` struct, which contains an
//! // uninitialized `name` field.
//! let resources = Arc::new(Resources::default());
//!
//! // Clone the Arc to emulate sharing with other threads, contexts,
//! // or data structures.
//! let res2 = resources.clone();
//!
//! // Here we emulate another thread accessing the shared data structure.
//! // NOTE: it's still our responsibility to ensure that the FreezeBox
//! // is initialized before anyone dereferences it.
//! //
//! let func = move || {
//!     // explicit deref
//!     assert_eq!(*res2.name, "Hello!");
//!     // implicit deref allows transparent access to inner methods
//!     assert_eq!(res2.name.len(), 6);
//! };
//!
//! resources.name.lazy_init("Hello!".to_string());
//! func();
//! ```
//! ## Not quite what you were looking for?
//!
//! There are many similar crates out there:
//! - [lazy_static](https://docs.rs/lazy_static)
//! - [once_cell](https://docs.rs/once_cell)
//! - [double-checked-cell](https://docs.rs/double-checked-cell)

#![no_std]

extern crate alloc;
use alloc::boxed::Box;
use core::any::type_name;
use core::marker::PhantomData;
use core::ops::Deref;
use core::sync::atomic::{AtomicPtr, Ordering};
use core::{mem, ptr};

/// `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>,
    phantom: PhantomData<T>,
}

impl<T> FreezeBox<T> {
    /// Create a new `FreezeBox` with optional initialization.
    ///
    /// A pre-inititialized `FreezeBox<T>` may not seem very useful, but it
    /// can be convenient when interacting with structs or interfaces that
    /// require a `FreezeBox`, e.g. unit tests.
    ///
    /// To always create an uninitialized `FreezeBox`, use
    /// `FreezeBox::default()`.
    ///
    pub fn new(val: Option<T>) -> Self {
        match val {
            None => Self::default(),
            Some(v) => {
                let fb = Self::default();
                fb.lazy_init(v);
                fb
            }
        }
    }

    /// 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));

        // Attempt to atomically swap from nullptr to `ptr`.
        //
        // Reasoning about the atomic ordering:
        // On the success side, we don't care about the load ordering,
        // only the store ordering, which must be `Release` or stronger.
        // This is because if we succeed, the previous value was null,
        // which is the state of a newly-created AtomicBox. So it's not
        // possible for us to race with another thread storing the null
        // pointer.
        //
        // On the failure side, we want to detect a race to double init,
        // so we want the load to be `Acquire` or stronger.
        //
        // Because the success ordering must be equal or stronger to the
        // failure ordering, we need to upgrade the success ordering to
        // `AcqRel`.
        //
        // If this succeeds, the FreezeBox is now initialized.
        if self
            .inner
            .compare_exchange(ptr::null_mut(), ptr, Ordering::AcqRel, Ordering::Acquire)
            .is_err()
        {
            // The compare_exchange failed, meaning a double-init was
            // attempted and we should panic.
            //
            // Before we do, retake ownership of the new pointer so that
            // we don't leak its memory.
            //
            // SAFETY: `ptr` was just created above using `Box::into_raw`.
            // Because compare_exchange failed, we know that it is still
            // the unique owner of the input value. So we can reclaim
            // ownership here and drop the result.

            let _val = unsafe { Box::<T>::from_raw(ptr) };

            panic!(
                "lazy_init on already-initialized FreezeBox<{}>",
                type_name::<T>()
            );
        }
    }

    /// Test whether a FreezeBox is initialized.
    pub fn is_initialized(&self) -> bool {
        let ptr = self.inner.load(Ordering::Acquire);
        !ptr.is_null()
    }

    /// Consume the FreezeBox and return its contents.
    pub fn into_inner(self) -> Option<T> {
        let ptr = self.inner.load(Ordering::Acquire);
        // Prevent Drop::drop() from being called on the FreezeBox
        // because we are transferring ownership elsewhere.
        mem::forget(self);
        if ptr.is_null() {
            return None;
        }

        // SAFETY: because we are consuming self, we must have sole ownership
        // of the FreezeBox contents. `lazy_init` created `ptr` from an
        // owning `Box<T>`, so it's safe for us to recreate that Box and drop
        // it.

        let tmp_box = unsafe { Box::from_raw(ptr) };
        Some(*tmp_box)
    }
}

impl<T> Deref for FreezeBox<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        let inner = self.inner.load(Ordering::Acquire);

        // SAFETY: the inner pointer can only be in two states:
        // 1. Uninitialized (null pointer): inner_ref will be None, and we
        //    should panic, because deref of an uninitialized FreezeBox is
        //    not allowed. Note we never create an actual &T (which would be
        //    undefined behavior).
        // 2. Initialized (valid shared pointer): inner_ref will be Some(&T).
        //    Because we own the inner memory, it's safe for us to hand out
        //    shared references to the inner T for as long as the FreezeBox
        //    lives.

        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::default(),
            phantom: PhantomData,
        }
    }
}

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.is_null() {
            // We own an inner object.  Re-hydrate into a Box<T> so that
            // T's destructor may run.
            //
            // SAFETY: We have exclusive access to the inner value, so we can
            // safely drop the contents. We could also reset the pointer, but
            // since this data structure is being dropped, this is the last
            // time that pointer will be seen; so there's no point.

            let _owned = unsafe { Box::<T>::from_raw(*inner) };
            // _owned will drop here.
        }
    }
}

/// Must fail to compile because FreezeBox<Rc> must not be Send.
/// ```compile_fail
/// use freezebox::FreezeBox;
/// use std::rc::Rc;
///
/// fn require_send<T: Send>(_t: &T) {}
///
/// let x = FreezeBox::<Rc<u32>>::new();
/// require_send(&x); // <- must fail to compile.
/// ```
///
/// Must fail to compile because FreezeBox<Cell> must not be Sync.
/// ```compile_fail
/// use freezebox::FreezeBox;
/// use std::cell::Cell;
///
/// fn require_sync<T: Sync>(_t: &T) {}
///
/// let x = FreezeBox::<Cell<u32>>::new();
/// require_sync(&x); // must fail to compile.
/// ```
struct _Unused {} // Only exists to get the compile-fail doctest

#[cfg(test)]
mod tests {
    use super::FreezeBox;
    use alloc::string::String;
    use alloc::string::ToString;
    use alloc::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::default();
        assert!(!y.is_initialized());
        y.lazy_init(x.clone());
        assert!(y.is_initialized());
        // 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]
    fn panic_deref() {
        let x = FreezeBox::<String>::default();
        // dot-operator implicitly deref's the FreezeBox.
        let _y = x.len();
    }

    #[test]
    #[should_panic]
    fn panic_double_init() {
        let x = FreezeBox::<String>::default();
        x.lazy_init("first".to_string());
        x.lazy_init("second".to_string());
    }

    #[test]
    fn consume_test() {
        let x = FreezeBox::<String>::default();
        x.lazy_init("hello".to_string());
        let x2: Option<String> = x.into_inner();
        assert_eq!(x2, Some("hello".to_string()));

        let y = FreezeBox::<String>::default();
        let y2: Option<String> = y.into_inner();
        assert_eq!(y2, None);
    }
}