enumizer/
result.rs

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