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();
    }
}