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}