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}