el_macro/
lib.rs

1/// Binds expression `e` to a new variable `n` if it ['has a value' / 'is ok'](IntoResult).
2/// Otherwise, executes the optional error handler `h` and evaluates the `f` expression.
3///
4/// Provides the ability to write concise code to get the value or get goin' in a context where
5/// [ErrorPropagationExpression (`?`)](https://doc.rust-lang.org/reference/expressions/operator-expr.html#r-expr.try)
6/// is not sufficient, such as when controlling execution flow with `break` or `continue`,
7/// or not applicable, as in function that does not return [`Result`] or [`Option`].
8///
9/// The new variable name `n` may contain `mut` keyword to create a mutable binding.
10/// 
11/// The error value is passed as the only argument to the `h` error handler
12/// if the latter is present. Determining whether the given expression `e` contains a valid
13/// value or and error (and what are they) is done through [`IntoResult`] trait,
14/// which is already implemented for [`Result`] and [`Option`].
15/// 
16/// The `f` expression is used to control the execution flow in a case when
17/// the `e`expression contains an error, making binding impossible.
18///
19/// # Examples
20///
21/// Basic usage:
22/// ```
23/// use el_macro::bind;
24///
25/// // binds `x` to the value 42, does not return
26/// bind!(x = Some(42), or return);
27/// assert_eq!(x, 42);
28///
29/// // creates a mutable binding `x` to the value 42, does not return
30/// bind!(mut x = Some(42), or return);
31/// x += 3;
32/// assert_eq!(x, 45);
33///
34/// // returns
35/// bind!(x = None::<i32>, or return);
36/// unreachable!();
37/// // returns as well, why wouldn't it
38/// bind!(mut x = None::<i32>, or return);
39/// unreachable!();
40/// ```
41///
42/// Handling error values:
43/// ```
44/// use el_macro::bind;
45///
46/// let okish = Some(42).ok_or("error");
47/// let errorish = None::<i32>.ok_or("error");
48///
49/// let handle_error = |err: &str| eprintln!("{err}!");
50///
51/// // binds `x` to the value 42, does not return
52/// bind!(x = okish, or handle_error, return);
53/// assert_eq!(x, 42);
54///
55/// 'omit_handler: {
56///     bind!(x = None::<i32>, or {
57///         // it's possible to omit the error handler
58///         // and perform handling in the flow control block
59///         eprintln!("no value for x");
60///         // you can control the execution flow however you like
61///         break 'omit_handler
62///     });
63///     unreachable!();
64/// }
65///
66/// // prints 'error!' and returns
67/// bind!(x = errorish, or handle_error, return);
68/// unreachable!();
69/// ```
70///
71/// Using with a custom type:
72/// ```
73/// use el_macro::{bind, IntoResult};
74///
75/// struct NegativeIsError(i32);
76///
77/// // returns some external descriptor on success,
78/// // negative number on failure
79/// fn external_get_descriptor(must_succeed: bool) -> i32 {
80///     // actual external call here
81///     if must_succeed { 42 } else { -1 }
82/// }
83///
84/// // successfully binds `x` to the value 42
85/// bind!(x = NegativeIsError(external_get_descriptor(true)), or return);
86/// assert_eq!(x, 42);
87///
88/// // prints 'error -1: unknown error' and returns
89/// bind!(x = NegativeIsError(external_get_descriptor(false)), or print_error, return);
90/// unreachable!();
91///
92/// // specifies how to determine whether `NegativeIsError`
93/// // contains a valid descriptor or an error
94/// impl IntoResult for NegativeIsError {
95///
96///     type Value = i32;
97///     type Error = ExternalCallError;
98///
99///     fn into_result(self) -> Result<Self::Value, Self::Error> {
100///         (self.0 >= 0)
101///             .then_some(self.0)
102///             .ok_or(ExternalCallError {
103///                 code: self.0,
104///                 desc: get_error_desc(self.0),
105///             })
106///     }
107///
108/// }
109///
110/// struct ExternalCallError {
111///     code: i32,
112///     desc: String,
113/// }
114///
115/// fn print_error(err: ExternalCallError) {
116///     let ExternalCallError { code, desc, .. } = err;
117///     eprintln!("error {code}: {desc}");
118/// }
119///
120/// fn get_error_desc(error_code: i32) -> String {
121///     if error_code >= 0 {
122///         "no_error".to_string()
123///     } else {
124///         "unknown error".to_string()
125///     }
126/// }
127/// ```
128#[macro_export]
129macro_rules! bind {
130
131    ($($n: ident)+ = $e: expr, or $f: expr) => {
132        let Ok($($n)+) = $crate::IntoResult::into_result($e) else { $f };
133    };
134
135    ($($n: ident)+ = $e: expr, or $h: expr, $f: expr) => {
136        let $($n)+ = match $crate::IntoResult::into_result($e) {
137            Ok($($n)+) => { $($n)+ },
138            Err(err) => {
139                $h(err);
140                $f
141            },
142        };
143    };
144
145}
146
147
148#[macro_export]
149macro_rules! if_matches {
150
151    ($e: expr, $p: pat => $f: expr) => {
152        if let $p = $e {
153            Some($f())
154        } else {
155            None
156        }
157    };
158
159}
160
161
162pub trait IntoResult {
163
164    type Value;
165    type Error;
166
167    fn into_result(self) -> Result<Self::Value, Self::Error>;
168
169}
170
171
172impl<T> IntoResult for Option<T> {
173
174    type Value = T;
175    type Error = ();
176
177    fn into_result(self) -> Result<Self::Value, Self::Error> {
178        self.ok_or(())
179    }
180
181}
182
183
184impl<T, E> IntoResult for Result<T, E> {
185
186    type Value = T;
187    type Error = E;
188
189    fn into_result(self) -> Result<Self::Value, Self::Error> {
190        self
191    }
192
193}