el_macro/
lib.rs

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