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
//! Provides an unsafe tagless alternative to `Option<T>` that uses less memory.
//!
//! Nightly-only. `#![no_std]`.

#![feature(untagged_unions, const_fn)]

#![no_std]

use core::mem::replace;

/// A union which either holds a `T` or nothing.
///
/// This can be seen as a `T` that may not be properly initialized.
///
/// In contrast to `Option<T>`, this type does not know if it stores a `T` or not, so it relies on
/// the caller to uphold safety. Consequently, `UntaggedOption` can be a bit smaller than `Option`
/// since it never has a discriminant. This makes it useful in resource-constrained environments or
/// other scenarios where the space overhead of `Option` is significant.
///
/// # Examples
///
/// ```
/// # extern crate untagged_option;
/// # use untagged_option::UntaggedOption;
/// # fn main() {
/// let mut opt = UntaggedOption::none();
///
/// // `opt` didn't hold a value before, so this assigment is fine.
/// // If it did, the value would be leaked.
/// opt = UntaggedOption::some("&str stored");
///
/// unsafe {
///     // Safe: `opt` is now properly initialized and holds a value.
///     assert_eq!(opt.as_ref(), &"&str stored");
///     let content = opt.take();   // `opt` is now uninitialized/none
/// }
/// # }
/// ```
///
/// # Safety
///
/// Since `UntaggedOption` does not have a discriminant, the user must know when the option contains
/// a valid value and only call the appropriate methods.
///
/// `UntaggedOption` does not destroy the contained value when dropped (it doesn't know if there
/// *is* a value), so the user must make sure to manually remove the value by calling `take` (or
/// only use `UntaggedOption` with `Copy` types that do not need to be dropped).
///
/// This also applies to assignments: An assignment like `opt = UntaggedOption::none()` will leak
/// the previously contained value (if any).
#[allow(unions_with_drop_fields)]
pub union UntaggedOption<T> {
    pub some: T,
    pub none: (),
}

impl<T> UntaggedOption<T> {
    /// Creates a new `UntaggedOption` holding no value.
    ///
    /// It is not safe to call any method on the resulting `UntaggedOption`.
    pub const fn none() -> Self {
        UntaggedOption {
            none: (),
        }
    }

    /// Creates an `UntaggedOption` containing `t`.
    ///
    /// # Note
    ///
    /// When the `UntaggedOption` is dropped, `t` will *not* be dropped automatically. You must call
    /// `take` if you need `t` to be dropped properly.
    pub const fn some(t: T) -> Self {
        UntaggedOption {
            some: t,
        }
    }

    /// Takes the `T` out of an initialized wrapper, making it uninitialized.
    ///
    /// This can be called to drop the contained `T`.
    ///
    /// # Safety
    ///
    /// Calling this method requires that `self` holds a valid `T`. [`UntaggedOption::some`] creates
    /// such an option.
    ///
    /// [`UntaggedOption::some`]: #method.some
    pub unsafe fn take(&mut self) -> T {
        replace(self, UntaggedOption::none()).some
    }

    /// Obtains an immutable reference to the contained `T`.
    ///
    /// # Safety
    ///
    /// Calling this method requires that `self` holds a valid `T`. [`UntaggedOption::some`] creates
    /// such an option.
    ///
    /// [`UntaggedOption::some`]: #method.some
    pub unsafe fn as_ref(&self) -> &T {
        &self.some
    }

    /// Obtains a mutable reference to the contained `T`.
    ///
    /// # Safety
    ///
    /// Calling this method requires that `self` holds a valid `T`. [`UntaggedOption::some`] creates
    /// such an option.
    ///
    /// [`UntaggedOption::some`]: #method.some
    pub unsafe fn as_mut(&mut self) -> &mut T {
        &mut self.some
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn static_context() {
        static mut MY_OPT: UntaggedOption<u8> = UntaggedOption::none();
        unsafe {
            MY_OPT = UntaggedOption::some(123);
            assert_eq!(*MY_OPT.as_ref(), 123);
            *MY_OPT.as_mut() = 42;
            assert_eq!(*MY_OPT.as_ref(), 42);
            MY_OPT.take();
        }
    }

    #[test]
    fn correct_drop() {
        use core::sync::atomic::{AtomicUsize, Ordering};

        static DROPCOUNT: AtomicUsize = AtomicUsize::new(0);

        struct MyDrop;
        impl Drop for MyDrop {
            fn drop(&mut self) {
                DROPCOUNT.fetch_add(1, Ordering::SeqCst);
            }
        }

        let mut opt = UntaggedOption::some(MyDrop);
        drop(opt);
        assert_eq!(DROPCOUNT.load(Ordering::SeqCst), 0);
        opt = UntaggedOption::some(MyDrop);
        assert_eq!(DROPCOUNT.load(Ordering::SeqCst), 0);
        unsafe { opt.take(); }
        assert_eq!(DROPCOUNT.load(Ordering::SeqCst), 1);
    }
}