std_fork\option/
untagged_option.rs

1use crate::zeroable::Zeroable;
2use core::ptr;
3
4/// A zero-optimized option type that uses no discriminant.
5///
6/// `UntaggedOption<T>` represents `None` by storing a zeroed value of `T`,
7/// and `Some(value)` by any non-zero `T`. This can save space, especially in FFI or low-level contexts.
8///
9/// # Safety
10///
11/// `T` **must be valid in its all-zero bit pattern**. Implementing `Zeroable` for a type that cannot
12/// safely be all-zero **invokes undefined behavior**.
13///
14/// ## ⚠️ Undefined Behavior example:
15///
16/// ```rust,ignore
17/// use core::num::NonZeroU32;
18/// use std_fork::zeroable::Zeroable;
19///
20/// unsafe impl Zeroable for NonZeroU32 {} // ⚠️ Undefined Behavior!
21/// ```
22///
23/// ## ✅ Safe example:
24///
25/// ```
26/// use std_fork::option::UntaggedOption;
27/// use std_fork::zeroable::Zeroable;
28///
29/// #[derive(Debug, PartialEq, Clone, Copy)]
30/// #[repr(C)]
31/// struct MyInt(u32);
32///
33/// unsafe impl Zeroable for MyInt {}
34///
35/// let opt = UntaggedOption::some(MyInt(123));
36/// assert!(opt.is_some());
37/// ```
38#[derive(Debug)]
39#[repr(transparent)]
40pub struct UntaggedOption<T: Zeroable> {
41    value: T,
42}
43
44impl<T> UntaggedOption<T>
45where
46    T: Zeroable,
47{
48    const ZEROED: T = unsafe { core::mem::zeroed() };
49    /// Size of the inner type `T`.
50    pub const T_SIZE: usize = core::mem::size_of::<T>();
51    /// True if `T` is a zero-sized type (ZST).
52    pub const IS_ZST: bool = Self::T_SIZE == 0;
53    /// A constant representing `None`.
54    pub const NONE: Self = Self { value: Self::ZEROED };
55
56    /// Creates a new `UntaggedOption` containing a value.
57    ///
58    /// # Example
59    ///
60    /// ```
61    /// use std_fork::option::UntaggedOption;
62    /// use std_fork::zeroable::Zeroable;
63    ///
64    /// #[derive(Clone, Copy, Debug, PartialEq)]
65    /// #[repr(transparent)]
66    /// struct Wrapper(u32);
67    ///
68    /// unsafe impl Zeroable for Wrapper {}
69    ///
70    /// let opt = UntaggedOption::some(Wrapper(1));
71    /// assert!(opt.is_some());
72    /// ```
73    #[inline]
74    pub const fn some(value: T) -> Self {
75        Self { value }
76    }
77
78    /// Creates a new `UntaggedOption` representing `None`.
79    ///
80    /// # Example
81    ///
82    /// ```
83    /// use std_fork::option::UntaggedOption;
84    ///
85    /// let none = UntaggedOption::<u32>::none();
86    /// assert!(none.is_none());
87    /// ```
88    #[inline]
89    pub const fn none() -> Self {
90        Self { value: Self::ZEROED }
91    }
92
93    /// Returns `true` if the option is `None`.
94    ///
95    /// # Note
96    ///
97    /// Zero-sized types (ZSTs) always return `true`.
98    ///
99    /// # Example
100    ///
101    /// ```
102    /// use std_fork::option::UntaggedOption;
103    ///
104    /// let none = UntaggedOption::<u32>::none();
105    /// assert!(none.is_none());
106    ///
107    /// let some = UntaggedOption::some(123u32);
108    /// assert!(!some.is_none());
109    /// ```
110    #[inline]
111    pub fn is_none(&self) -> bool {
112        use core::slice::from_raw_parts;
113
114        if Self::IS_ZST {
115            return true;
116        }
117
118        unsafe {
119            let value_bytes = from_raw_parts((&self.value as *const T) as *const u8, Self::T_SIZE);
120
121            let zero_slice = Self::ZEROED; // NOTE: This is necessary because of dangling UB if not bound to a variable!
122            let zero_bytes = from_raw_parts((&zero_slice as *const T) as *const u8, Self::T_SIZE);
123            value_bytes == zero_bytes
124        }
125    }
126
127    /// Returns `true` if the option contains a value.
128    ///
129    /// # Example
130    ///
131    /// ```
132    /// use std_fork::option::UntaggedOption;
133    ///
134    /// let opt = UntaggedOption::some(7i32);
135    /// assert!(opt.is_some());
136    ///
137    /// let none = UntaggedOption::<()>::none();
138    /// assert!(!none.is_some());
139    /// ```
140    #[inline]
141    pub fn is_some(&self) -> bool {
142        !self.is_none()
143    }
144
145    /// Takes the value out of the option, leaving a `None` in its place.
146    ///
147    /// # Example
148    ///
149    /// ```
150    /// use std_fork::option::UntaggedOption;
151    /// use std_fork::zeroable::Zeroable;
152    ///
153    /// #[derive(Clone, Copy, Debug, PartialEq)]
154    /// #[repr(C)]
155    /// struct MyInt(i32);
156    ///
157    /// unsafe impl Zeroable for MyInt {}
158    ///
159    /// let mut opt = UntaggedOption::some(MyInt(42));
160    /// let taken = opt.take();
161    ///
162    /// assert_eq!(taken, Some(MyInt(42)));
163    /// assert!(opt.is_none());
164    /// ```
165    #[inline]
166    pub fn take(&mut self) -> Option<T> {
167        if self.is_none() {
168            return None;
169        }
170
171        let taken = unsafe { ptr::read(&self.value) };
172        unsafe { ptr::write(&mut self.value as *mut T, Self::ZEROED) };
173
174        Some(taken)
175    }
176
177    /// Returns a reference to the value if `Some`, otherwise returns `None`.
178    ///
179    /// # Example
180    ///
181    /// ```
182    /// use std_fork::option::UntaggedOption;
183    ///
184    /// let opt = UntaggedOption::some(999u32);
185    /// assert_eq!(opt.as_ref(), Some(&999));
186    ///
187    /// let none = UntaggedOption::<u32>::none();
188    /// assert_eq!(none.as_ref(), None);
189    /// ```
190    #[inline]
191    pub fn as_ref(&self) -> Option<&T> {
192        if self.is_none() {
193            return None;
194        }
195
196        Some(&self.value)
197    }
198
199    /// Returns a mutable reference to the value if `Some`, otherwise returns `None`.
200    ///
201    /// # Example
202    ///
203    /// ```
204    /// use std_fork::option::UntaggedOption;
205    ///
206    /// let mut opt = UntaggedOption::some(100i32);
207    ///
208    /// if let Some(val) = opt.as_mut() {
209    ///     *val += 1;
210    /// }
211    ///
212    /// assert_eq!(opt.as_ref(), Some(&101));
213    /// ```
214    #[inline]
215    pub fn as_mut(&mut self) -> Option<&mut T> {
216        if self.is_none() {
217            return None;
218        }
219
220        Some(&mut self.value)
221    }
222}
223
224impl<T> From<Option<T>> for UntaggedOption<T>
225where
226    T: Zeroable,
227{
228    /// Converts a standard `Option<T>` into an `UntaggedOption<T>`.
229    ///
230    /// # Example
231    ///
232    /// ```
233    /// use std_fork::option::UntaggedOption;
234    ///
235    /// let opt = UntaggedOption::from(Some(255u8));
236    /// assert!(opt.is_some());
237    ///
238    /// let none = UntaggedOption::from(None::<u8>);
239    /// assert!(none.is_none());
240    /// ```
241    #[inline]
242    fn from(value: Option<T>) -> Self {
243        match value {
244            Some(v) => Self::some(v),
245            None => Self::none(),
246        }
247    }
248}
249
250impl<T: PartialEq + Zeroable> PartialEq for UntaggedOption<T> {
251    fn eq(&self, other: &Self) -> bool {
252        match (self.is_some(), other.is_some()) {
253            (false, false) => true,
254            (true, true) => self.value == other.value,
255            _ => false,
256        }
257    }
258}