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