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