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///
5/// Provides the ability to write concise code to get the value or get goin' in a context where
6/// [ErrorPropagationExpression (`?`)](https://doc.rust-lang.org/reference/expressions/operator-expr.html#r-expr.try)
7/// is not sufficient, such as when controlling execution flow with `break` or `continue`,
8/// or not applicable, as in function that does not return [`Result`] or [`Option`].
9///
10/// The new variable name `n` may contain `mut` keyword to create a mutable binding.
11/// 
12/// The error value is passed as the only argument to the `h` error handler
13/// if the latter is present. Determining whether the given expression `e` contains a valid
14/// value or and error (and what are they) is done through [`IntoResult`] trait,
15/// which is already implemented for [`Result`] and [`Option`].
16/// 
17/// The `f` expression is used to control the execution flow in a case when
18/// the `e`expression contains an error, making binding impossible.
19///
20///
21/// # Syntax
22///
23/// ```text
24/// bind!([mut] <variable-name> = <value-expression>, or [<error-handler>,] <flow-control-expression>);
25/// ```
26///
27///
28/// # Examples
29///
30/// Basic usage:
31/// ```
32/// # use el_macro::bind;
33/// #
34/// // binds `x` to the value 42, does not return
35/// bind!(x = Some(42), or return);
36/// assert_eq!(x, 42);
37///
38/// // creates a mutable binding `x` to the value 42, does not return
39/// bind!(mut x = Some(42), or return);
40/// x += 3;
41/// assert_eq!(x, 45);
42///
43/// // returns
44/// bind!(x = None::<i32>, or return);
45/// unreachable!();
46/// // returns as well, why wouldn't it
47/// bind!(mut x = None::<i32>, or return);
48/// unreachable!();
49/// ```
50///
51/// Handling error values:
52/// ```
53/// # use el_macro::bind;
54/// #
55/// let okish = Some(42).ok_or("error");
56/// let errorish = None::<i32>.ok_or("error");
57///
58/// let handle_error = |err: &str| eprintln!("{err}!");
59///
60/// // binds `x` to the value 42, does not return
61/// bind!(x = okish, or handle_error, return);
62/// assert_eq!(x, 42);
63///
64/// 'omit_handler: {
65///     bind!(x = None::<i32>, or {
66///         // it's possible to omit the error handler
67///         // and perform handling in the flow control block
68///         eprintln!("no value for x");
69///         // you can control the execution flow however you like
70///         break 'omit_handler
71///     });
72///     unreachable!();
73/// }
74///
75/// // prints 'error!' and returns
76/// bind!(x = errorish, or handle_error, return);
77/// unreachable!();
78/// ```
79///
80/// Using with a custom type:
81/// ```
82/// # use el_macro::{bind, IntoResult};
83/// #
84/// struct NegativeIsError(i32);
85///
86/// // returns some external descriptor on success,
87/// // negative number on failure
88/// fn external_get_descriptor(must_succeed: bool) -> i32 {
89///     // actual external call here
90///     if must_succeed { 42 } else { -1 }
91/// }
92///
93/// // successfully binds `x` to the value 42
94/// bind!(x = NegativeIsError(external_get_descriptor(true)), or return);
95/// assert_eq!(x, 42);
96///
97/// // prints 'error -1: unknown error' and returns
98/// bind!(x = NegativeIsError(external_get_descriptor(false)), or print_error, return);
99/// unreachable!();
100///
101/// // specifies how to determine whether `NegativeIsError`
102/// // contains a valid descriptor or an error
103/// impl IntoResult for NegativeIsError {
104///
105///     type Value = i32;
106///     type Error = ExternalCallError;
107///
108///     fn into_result(self) -> Result<Self::Value, Self::Error> {
109///         (self.0 >= 0)
110///             .then_some(self.0)
111///             .ok_or(ExternalCallError {
112///                 code: self.0,
113///                 desc: get_error_desc(self.0),
114///             })
115///     }
116///
117/// }
118///
119/// struct ExternalCallError {
120///     code: i32,
121///     desc: String,
122/// }
123///
124/// fn print_error(err: ExternalCallError) {
125///     let ExternalCallError { code, desc, .. } = err;
126///     eprintln!("error {code}: {desc}");
127/// }
128///
129/// fn get_error_desc(error_code: i32) -> String {
130///     if error_code >= 0 {
131///         "no_error".to_string()
132///     } else {
133///         "unknown error".to_string()
134///     }
135/// }
136/// ```
137#[macro_export]
138macro_rules! bind {
139
140    ($($n: ident)+ = $e: expr, or $f: expr) => {
141        let Ok($($n)+) = $crate::IntoResult::into_result($e) else { $f };
142    };
143
144    ($($n: ident)+ = $e: expr, or $h: expr, $f: expr) => {
145        let $($n)+ = match $crate::IntoResult::into_result($e) {
146            Ok($($n)+) => { $($n)+ },
147            Err(err) => {
148                $h(err);
149                $f
150            },
151        };
152    };
153
154}
155
156
157/// Maps pattern's bound variables to `Some` if the provided expression matches the pattern.
158///
159/// Evaluates the expression `e` against the pattern `p` and maps
160/// the bound variables of `p` into `Some` if the expression matches
161/// and the optional match guard expression `c` evaluates to `true`.
162///
163/// Mapping is performed by the closure, the body of which is provided as the `m` argument.
164/// Inside `m`, the bound variables of `p` as well as variables from the outer scope are available.
165///
166/// Yields `None` if the expression does not match the pattern.
167///
168///
169/// # Syntax
170///
171/// ```text
172/// if_matches!(<expression>, <pattern> [if <match-guard>] => <mapping-closure-body>)
173/// ```
174///
175///
176/// # Examples
177///
178/// Basic usage:
179/// ```
180/// # use el_macro::if_matches;
181/// #
182/// let a = Some(41);
183/// let b = Some(43);
184/// let avg = |x: i32, y: i32| (x + y) / 2;
185///
186/// let x = if_matches!((a, b), (Some(x), Some(y)) => avg(x, y));
187/// assert!(x.is_some_and(|val| val == 42));
188///
189/// let x = if_matches!((a, None::<u8>), (Some(x), Some(_)) => a);
190/// assert!(x.is_none());
191/// ```
192///
193/// Usage with match guard:
194/// ```
195/// # use el_macro::if_matches;
196/// #
197/// let vol = Some(100);
198///
199/// let bins = Some(25);
200/// let per_bin = if_matches!((vol, bins), (Some(v), Some(b)) if b != 0 => v / b);
201/// assert!(per_bin.is_some_and(|share| share == 4));
202///
203/// let bins = Some(0);
204/// let per_bin = if_matches!((vol, bins), (Some(v), Some(b)) if b != 0 => v / b);
205/// assert!(per_bin.is_none());
206/// ```
207#[macro_export]
208macro_rules! if_matches {
209
210    ($e: expr, $p: pat $(if $c:expr)? => $m: expr) => {
211        match $e {
212            $p $(if $c)? => Some((|| $m)()),
213            _ => None,
214        }
215    };
216
217}
218
219
220/// Tells the `bind` macro whether the given expression yielded a bindable value or an error.
221///
222/// Enables the `bind` macro to determine by representing the value yielded
223/// by the given expression as `Result` whether to create a variable and bind it to the value,
224/// or to call the optional error handler and evaluate the execution flow control block.
225///
226/// Implemented by default for `Result` and `Option`, with `()` as `Error` for the latter.
227///
228/// For the usage example, refer to the `bind` macro documentation, which includes
229/// an example of using it with user-defined types.
230pub trait IntoResult {
231
232    /// Type of the value that the `bind` macro binds the created variable to.
233    type Value;
234    /// Type of the error that the `bind` macro passes as the only argument
235    /// to the optional error handler.
236    type Error;
237
238    /// Represents the yielded value as `Result`
239    fn into_result(self) -> Result<Self::Value, Self::Error>;
240
241}
242
243
244impl<T> IntoResult for Option<T> {
245
246    type Value = T;
247    type Error = ();
248
249    fn into_result(self) -> Result<Self::Value, Self::Error> {
250        self.ok_or(())
251    }
252
253}
254
255
256impl<T, E> IntoResult for Result<T, E> {
257
258    type Value = T;
259    type Error = E;
260
261    fn into_result(self) -> Result<Self::Value, Self::Error> {
262        self
263    }
264
265}