el_macro/bind/mod.rs
1//! The [`crate::bind!`] macro and related [`crate::bind::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/// # Syntax
25///
26/// ```text
27/// bind!([mut] <var-name> [= <value-expr>], or [<err-handler>,] <flow-ctl>);
28/// ```
29///
30/// - `mut` — indicator keyword to make the binding mutable.
31/// - `<var-name>` — name of the newly created variable.
32/// - `<value-expr>` — expression whose value is [being tested](IntoResult) to contain
33/// an unwrappable value. If not specified, the existing value of the variable `<var-name>`
34/// will be used to create new variable with the same name.
35/// - `<err-handler>` — optional error handler that is called if there's no value to unwrap,
36/// with error object passed as the only argument.
37/// - `<flow-ctl>` — expression used to control the execution flow in a case
38/// when there's no value to unwrap.
39///
40/// # Examples
41///
42/// Basic usage:
43/// ```
44/// # use el_macro::bind;
45/// #
46/// // binds `x` to the value 42, does not return
47/// bind!(x = Some(42), or return);
48/// assert_eq!(x, 42);
49///
50/// // creates a mutable binding `x` to the value 42, does not return
51/// bind!(mut x = Some(42), or return);
52/// x += 3;
53/// assert_eq!(x, 45);
54///
55/// // returns
56/// bind!(x = None::<i32>, or return);
57/// unreachable!();
58/// // returns as well, why wouldn't it
59/// bind!(mut x = None::<i32>, or return);
60/// unreachable!();
61/// ```
62///
63/// Handling error values:
64/// ```
65/// # use el_macro::bind;
66/// #
67/// let okish = Some(42).ok_or("error");
68/// let errorish = None::<i32>.ok_or("error");
69///
70/// let handle_error = |err: &str| eprintln!("{err}!");
71///
72/// // binds `x` to the value 42, does not return
73/// bind!(x = okish, or handle_error, return);
74/// assert_eq!(x, 42);
75///
76/// 'omit_handler: {
77/// bind!(x = None::<i32>, or {
78/// // it's possible to omit the error handler
79/// // and perform handling in the flow control block
80/// eprintln!("no value for x");
81/// // you can control the execution flow however you like
82/// break 'omit_handler
83/// });
84/// unreachable!();
85/// }
86///
87/// // prints 'error!' and returns
88/// bind!(x = errorish, or handle_error, return);
89/// unreachable!();
90/// ```
91///
92/// Omitting the `<value-expr>`:
93/// ```
94/// # use el_macro::bind;
95/// #
96/// let x = Some(42);
97/// bind!(x /* = x */, or return);
98/// assert_eq!(x, 42);
99/// ```
100///
101/// Using with a custom type:
102/// ```
103/// # use el_macro::{bind, bind::IntoResult};
104/// #
105/// struct NegativeIsError(i32);
106///
107/// // returns some external descriptor on success,
108/// // negative number on failure
109/// fn external_get_descriptor(must_succeed: bool) -> i32 {
110/// // actual external call here
111/// if must_succeed { 42 } else { -1 }
112/// }
113///
114/// // successfully binds `x` to the value 42
115/// bind!(x = NegativeIsError(external_get_descriptor(true)), or return);
116/// assert_eq!(x, 42);
117///
118/// // prints 'error -1: unknown error' and returns
119/// bind!(x = NegativeIsError(external_get_descriptor(false)), or print_error, return);
120/// unreachable!();
121///
122/// // specifies how to determine whether `NegativeIsError`
123/// // contains a valid descriptor or an error
124/// impl IntoResult for NegativeIsError {
125///
126/// type Value = i32;
127/// type Error = ExternalCallError;
128///
129/// fn into_result(self) -> Result<Self::Value, Self::Error> {
130/// (self.0 >= 0)
131/// .then_some(self.0)
132/// .ok_or(ExternalCallError {
133/// code: self.0,
134/// desc: get_error_desc(self.0),
135/// })
136/// }
137///
138/// }
139///
140/// struct ExternalCallError {
141/// code: i32,
142/// desc: String,
143/// }
144///
145/// fn print_error(err: ExternalCallError) {
146/// let ExternalCallError { code, desc, .. } = err;
147/// eprintln!("error {code}: {desc}");
148/// }
149///
150/// fn get_error_desc(error_code: i32) -> String {
151/// if error_code >= 0 { "no error" } else { "unknown error "}.into()
152/// }
153/// ```
154#[macro_export]
155macro_rules! bind {
156
157 ($n: ident = $e: expr, or $f: expr) => {
158 let $n = {
159 use $crate::bind::IntoResult;
160 match $e.into_result() {
161 Ok(val) => val,
162 Err(_) => $f,
163 }
164 };
165 };
166
167 (mut $n: ident = $e: expr, or $f: expr) => {
168 let mut $n = {
169 $crate::bind!($n = $e, or $f);
170 $n
171 };
172 };
173
174 ($n: ident, or $f: expr) => {
175 $crate::bind!($n = $n, or $f);
176 };
177
178 (mut $n: ident, or $f: expr) => {
179 $crate::bind!(mut $n = $n, or $f);
180 };
181
182 ($n: ident = $e: expr, or $h: expr, $f: expr) => {
183 let $n = {
184 use $crate::bind::IntoResult;
185 match $e.into_result() {
186 Ok($n) => { $n },
187 Err(err) => {
188 $h(err);
189 $f
190 },
191 }
192 };
193 };
194
195 (mut $n: ident = $e: expr, or $h: expr, $f: expr) => {
196 let mut $n = {
197 $crate::bind!($n = $e, or $h, $f);
198 $n
199 };
200 };
201
202 ($n: ident, or $h: expr, $f: expr) => {
203 $crate::bind!($n = $n, or $h, $f);
204 };
205
206 (mut $n: ident, or $h: expr, $f: expr) => {
207 $crate::bind!(mut $n = $n, or $h, $f);
208 };
209
210}