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}