enumizer/
option.rs

1/// Creates an Option-like enum with custom variant names.
2///
3/// See [`examples::OptionExample`](crate::examples::OptionExample) for a generated example.
4///
5/// # Example
6///
7/// ```
8/// use enumizer::alias_option;
9///
10/// alias_option!(Value, Found, Searching);
11///
12/// let searching: Value<i32> = Value::Searching;
13/// let found = Value::Found(42);
14///
15/// assert!(searching.is_searching());
16/// assert!(!searching.is_found());
17/// assert!(found.is_found());
18/// assert_eq!(found.as_found(), Some(&42));
19/// ```
20///
21/// # Generated Methods
22///
23/// ```
24/// use enumizer::alias_option;
25/// alias_option!(Value, Found, Searching);
26/// let mut val = Value::Found(10);
27///
28/// // Check variants
29/// assert!(val.is_found());
30///
31/// // Get references
32/// assert_eq!(val.as_found(), Some(&10));
33/// assert_eq!(val.as_found_mut(), Some(&mut 10));
34///
35/// // Transform
36/// let doubled = val.map(|x| x * 2);
37/// assert_eq!(doubled.unwrap(), 20);
38///
39/// // Unwrap variants
40/// assert_eq!(Value::Found(5).unwrap_or(0), 5);
41/// assert_eq!(Value::<i32>::Searching.unwrap_or(5), 5);
42/// ```
43///
44/// # Conversions
45///
46/// The generated type can be easily converted to and from `Option<T>`.
47///
48/// ```
49/// use enumizer::alias_option;
50/// alias_option!(Value, Found, Searching);
51/// let from_some: Value<i32> = Some(42).into();
52/// let from_none: Value<i32> = None.into();
53///
54/// assert_eq!(from_some, Value::Found(42));
55/// assert_eq!(from_none, Value::Searching);
56///
57/// let to_option: Option<i32> = Value::Found(42).into();
58/// assert_eq!(to_option, Some(42));
59/// ```
60///
61/// # Conditional Checks
62///
63/// ```
64/// use enumizer::alias_option;
65/// alias_option!(Value, Found, Searching);
66/// let val = Value::Found(42);
67/// assert!(val.is_found_and(|&x| x > 40));
68/// assert!(!val.is_found_and(|&x| x < 40));
69/// assert!(!Value::<i32>::Searching.is_found_and(|&x| x > 40));
70///
71/// assert!(Value::<i32>::Searching.is_searching_or(|&x| x > 40));
72/// assert!(val.is_searching_or(|&x| x > 40));
73/// assert!(!val.is_searching_or(|&x| x < 40));
74/// ```
75///
76/// # Custom Traits
77///
78/// You can specify custom traits to derive instead of the default set.
79/// Use the `traits:` keyword followed by a list of trait names in brackets.
80///
81/// ```
82/// use enumizer::alias_option;
83/// alias_option!(CustomOption, Present, Absent, traits: [Debug, Clone, serde::Serialize, serde::Deserialize]);
84/// let val = CustomOption::Present(42);
85/// assert_eq!(format!("{:?}", val), "Present(42)");
86/// assert_eq!(val.clone().unwrap(), 42);
87/// let json = serde_json::to_string(&val).unwrap();
88/// assert_eq!(json, r#"{"Present":42}"#);
89/// ```
90///
91/// # Try Trait Support (Nightly Only)
92///
93/// Add `implement_try` to enable the `?` operator for early returns.
94/// Requires nightly Rust with `#![feature(try_trait_v2)]`.
95///
96/// ```ignore
97/// #![feature(try_trait_v2)]
98/// use enumizer::alias_option;
99///
100/// alias_option!(Value, Found, Searching, implement_try);
101///
102/// fn try_example(val1: Value<i32>, val2: Value<i32>) -> Value<i32> {
103///     let x = val1?;
104///     let y = val2?;
105///     Value::Found(x + y)
106/// }
107///
108/// assert_eq!(try_example(Value::Found(10), Value::Found(20)), Value::Found(30));
109/// assert_eq!(try_example(Value::Searching, Value::Found(20)), Value::Searching);
110/// ```
111
112#[macro_export]
113macro_rules! alias_option {
114    ($type_name:ident, $some_variant:ident, $none_variant:ident) => {
115        $crate::alias_option!($type_name, $some_variant, $none_variant, [Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash]);
116    };
117    ($type_name:ident, $some_variant:ident, $none_variant:ident, traits: [$($trait:path),*]) => {
118        $crate::alias_option!($type_name, $some_variant, $none_variant, [$($trait),*]);
119    };
120    ($type_name:ident, $some_variant:ident, $none_variant:ident, implement_try) => {
121        $crate::alias_option!($type_name, $some_variant, $none_variant, [Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash], implement_try);
122    };
123    ($type_name:ident, $some_variant:ident, $none_variant:ident, traits: [$($trait:path),*], implement_try) => {
124        $crate::alias_option!($type_name, $some_variant, $none_variant, [$($trait),*], implement_try);
125    };
126    ($type_name:ident, $some_variant:ident, $none_variant:ident, [$($trait:path),*]) => {
127        $crate::alias_option!($type_name, $some_variant, $none_variant, [$($trait),*], );
128    };
129    ($type_name:ident, $some_variant:ident, $none_variant:ident, [$($trait:path),*], $($implement_try:ident)?) => {
130      paste::paste! {
131		#[derive($($trait),*)]
132		pub enum $type_name<T> {
133			$none_variant,
134			$some_variant(T),
135		}
136
137		impl<T> $type_name<T> {
138			/// Behaves like [`Option::is_none`](https://doc.rust-lang.org/std/option/enum.Option.html#method.is_none)
139			pub fn [<is_ $none_variant:lower>](&self) -> bool {
140				matches!(self, $type_name::$none_variant)
141			}
142
143			/// Behaves like [`Option::is_some`](https://doc.rust-lang.org/std/option/enum.Option.html#method.is_some)
144			pub fn [<is_ $some_variant:lower>](&self) -> bool {
145				matches!(self, $type_name::$some_variant(_))
146			}
147
148			/// Behaves like [`Option::is_some_and`](https://doc.rust-lang.org/std/option/enum.Option.html#method.is_some_and)
149			pub fn [<is_ $some_variant:lower _and>]<F: FnOnce(&T) -> bool>(&self, f: F) -> bool {
150				match self {
151					$type_name::$some_variant(v) => f(v),
152					_ => false,
153				}
154			}
155
156			/// Behaves like [`Option::is_none_or`](https://doc.rust-lang.org/std/option/enum.Option.html#method.is_none_or)
157			pub fn [<is_ $none_variant:lower _or>]<F: FnOnce(&T) -> bool>(&self, f: F) -> bool {
158				match self {
159					$type_name::$none_variant => true,
160					$type_name::$some_variant(v) => f(v),
161				}
162			}
163
164			/// Behaves like [`Option::as_ref`](https://doc.rust-lang.org/std/option/enum.Option.html#method.as_ref)
165			pub fn [<as_ $some_variant:lower>](&self) -> Option<&T> {
166				match self {
167					$type_name::$some_variant(v) => Some(v),
168					_ => None,
169				}
170			}
171
172			/// Behaves like [`Option::as_mut`](https://doc.rust-lang.org/std/option/enum.Option.html#method.as_mut)
173			pub fn [<as_ $some_variant:lower _mut>](&mut self) -> Option<&mut T> {
174				match self {
175					$type_name::$some_variant(v) => Some(v),
176					_ => None,
177				}
178			}
179
180			/// Behaves like [`Option::map`](https://doc.rust-lang.org/std/option/enum.Option.html#method.map)
181			pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> $type_name<U> {
182				match self {
183					$type_name::$some_variant(v) => $type_name::$some_variant(f(v)),
184					$type_name::$none_variant => $type_name::$none_variant,
185				}
186			}
187
188			/// Behaves like [`Option::unwrap`](https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap)
189			pub fn unwrap(self) -> T {
190				match self {
191					$type_name::$some_variant(v) => v,
192					$type_name::$none_variant => {
193						panic!("called `unwrap()` on a `{}`", stringify!($none_variant))
194					}
195				}
196			}
197
198			/// Behaves like [`Option::unwrap_or`](https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap_or)
199			pub fn unwrap_or(self, default: T) -> T {
200				match self {
201					$type_name::$some_variant(v) => v,
202					$type_name::$none_variant => default,
203				}
204			}
205
206			/// Behaves like [`Option::unwrap_or_else`](https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap_or_else)
207			pub fn unwrap_or_else<F: FnOnce() -> T>(self, f: F) -> T {
208				match self {
209					$type_name::$some_variant(v) => v,
210					$type_name::$none_variant => f(),
211				}
212			}
213		}
214
215		impl<T> From<Option<T>> for $type_name<T> {
216			fn from(opt: Option<T>) -> Self {
217				match opt {
218					Some(v) => $type_name::$some_variant(v),
219					None => $type_name::$none_variant,
220				}
221			}
222		}
223
224		impl<T> From<$type_name<T>> for Option<T> {
225			fn from(val: $type_name<T>) -> Self {
226				match val {
227					$type_name::$some_variant(v) => Some(v),
228					$type_name::$none_variant => None,
229				}
230			}
231		}
232        }
233
234        $(
235            let _ = stringify!($implement_try);
236            paste::paste! {
237                impl<T> std::ops::Try for $type_name<T> {
238                    type Output = T;
239                    type Residual = $type_name<std::convert::Infallible>;
240
241                    fn from_output(output: Self::Output) -> Self {
242                        $type_name::$some_variant(output)
243                    }
244
245                    fn branch(self) -> std::ops::ControlFlow<Self::Residual, Self::Output> {
246                        match self {
247                            $type_name::$some_variant(v) => std::ops::ControlFlow::Continue(v),
248                            $type_name::$none_variant => std::ops::ControlFlow::Break($type_name::$none_variant),
249                        }
250                    }
251                }
252
253                impl<T> std::ops::FromResidual for $type_name<T> {
254                    fn from_residual(_: $type_name<std::convert::Infallible>) -> Self {
255                        $type_name::$none_variant
256                    }
257                }
258            }
259        )?
260    }
261  }
262
263#[cfg(test)]
264mod tests {
265    #[test]
266    fn size_equivalence() {
267        use std::num::NonZeroU32;
268        alias_option!(Value, Found, Searching);
269        assert_eq!(
270            std::mem::size_of::<Value<i32>>(),
271            std::mem::size_of::<Option<i32>>()
272        );
273        assert_eq!(
274            std::mem::size_of::<Value<NonZeroU32>>(),
275            std::mem::size_of::<Option<NonZeroU32>>()
276        );
277    }
278}