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}