if_chain/
lib.rs

1//! This crate provides a single macro called `if_chain!`.
2//!
3//! `if_chain!` lets you write long chains of nested `if` and `if let`
4//! statements without the associated rightward drift. It also supports multiple
5//! patterns (e.g. `if let Foo(a) | Bar(a) = b`) in places where Rust would
6//! normally not allow them.
7//!
8//! See the associated [blog post] for the background behind this crate.
9//!
10//! [blog post]: https://lambda.xyz/blog/if-chain
11//!
12//! # Note about recursion limits
13//!
14//! If you run into "recursion limit reached" errors while using this macro, try
15//! adding
16//!
17//! ```rust,ignore
18//! #![recursion_limit = "1000"]
19//! ```
20//!
21//! to the top of your crate.
22//!
23//! # Examples
24//!
25//! ## Quick start
26//!
27//! ```rust,ignore
28//! if_chain! {
29//!     if let Some(y) = x;
30//!     if y.len() == 2;
31//!     if let Some(z) = y;
32//!     then {
33//!         do_stuff_with(z);
34//!     }
35//! }
36//! ```
37//!
38//! becomes
39//!
40//! ```rust,ignore
41//! if let Some(y) = x {
42//!     if y.len() == 2 {
43//!         if let Some(z) = y {
44//!             do_stuff_with(z);
45//!         }
46//!     }
47//! }
48//! ```
49//!
50//! ## Fallback values with `else`
51//!
52//! ```rust,ignore
53//! if_chain! {
54//!     if let Some(y) = x;
55//!     if let Some(z) = y;
56//!     then {
57//!         do_stuff_with(z)
58//!     } else {
59//!         do_something_else()
60//!     }
61//! }
62//! ```
63//!
64//! becomes
65//!
66//! ```rust,ignore
67//! if let Some(y) = x {
68//!     if let Some(z) = y {
69//!         do_stuff_with(z)
70//!     } else {
71//!         do_something_else()
72//!     }
73//! } else {
74//!     do_something_else()
75//! }
76//! ```
77//!
78//! ## Intermediate variables with `let`
79//!
80//! ```rust,ignore
81//! if_chain! {
82//!     if let Some(y) = x;
83//!     let z = y.some().complicated().expression();
84//!     if z == 42;
85//!     then {
86//!        do_stuff_with(y);
87//!     }
88//! }
89//! ```
90//!
91//! becomes
92//!
93//! ```rust,ignore
94//! if let Some(y) = x {
95//!     let z = y.some().complicated().expression();
96//!     if z == 42 {
97//!         do_stuff_with(y);
98//!     }
99//! }
100//! ```
101//!
102//! ## Type ascription
103//!
104//! ```rust,ignore
105//! let mut x = some_generic_computation();
106//! if_chain! {
107//!     if x > 7;
108//!     let y: u32 = another_generic_computation();
109//!     then { x += y }
110//!     else { x += 1 }
111//! }
112//! ```
113//!
114//! becomes
115//!
116//! ```rust,ignore
117//! let mut x = some_generic_computation();
118//! if x > 7 {
119//!     let y: u32 = another_generic_computation();
120//!     x += y
121//! } else {
122//!     x += 1
123//! }
124//! ```
125//!
126//! ## Multiple patterns
127//!
128//! ```rust,ignore
129//! if_chain! {
130//!     if let Foo(y) | Bar(y) | Baz(y) = x;
131//!     let Bubbles(z) | Buttercup(z) | Blossom(z) = y;
132//!     then { do_stuff_with(z) }
133//! }
134//! ```
135//!
136//! becomes
137//!
138//! ```rust,ignore
139//! match x {
140//!     Foo(y) | Bar(y) | Baz(y) => match y {
141//!         Bubbles(z) | Buttercup(z) | Blossom(z) => do_stuff_with(z)
142//!     },
143//!     _ => {}
144//! }
145//! ```
146//!
147//! Note that if you use a plain `let`, then `if_chain!` assumes that the
148//! pattern is *irrefutable* (always matches) and doesn't add a fallback branch.
149
150#![cfg_attr(not(test), no_std)]
151
152/// Macro for writing nested `if let` expressions.
153///
154/// See the crate documentation for information on how to use this macro.
155#[macro_export(local_inner_macros)]
156macro_rules! if_chain {
157    ($($tt:tt)*) => {
158        __if_chain! { @init () $($tt)* }
159    };
160}
161
162#[doc(hidden)]
163#[macro_export(local_inner_macros)]
164macro_rules! __if_chain {
165    // Expand with both a successful case and a fallback
166    (@init ($($tt:tt)*) then { $($then:tt)* } else { $($other:tt)* }) => {
167        __if_chain! { @expand { $($other)* } $($tt)* then { $($then)* } }
168    };
169    // Expand with no fallback
170    (@init ($($tt:tt)*) then { $($then:tt)* }) => {
171        __if_chain! { @expand {} $($tt)* then { $($then)* } }
172    };
173    // Munch everything until either of the arms above can be matched.
174    // Munched tokens are placed into `$($tt)*`
175    (@init ($($tt:tt)*) $head:tt $($tail:tt)*) => {
176        __if_chain! { @init ($($tt)* $head) $($tail)* }
177    };
178
179    // `let` with a single pattern
180    (@expand { $($other:tt)* } let $pat:pat = $expr:expr; $($tt:tt)+) => {
181        {
182            let $pat = $expr;
183            __if_chain! { @expand { $($other)* } $($tt)+ }
184        }
185    };
186    // `let` with a single identifier and a type hint
187    (@expand { $($other:tt)* } let $ident:ident: $ty:ty = $expr:expr; $($tt:tt)+) => {
188        {
189            let $ident: $ty = $expr;
190            __if_chain! { @expand { $($other)* } $($tt)+ }
191        }
192    };
193    // `let` with multiple patterns
194    (@expand { $($other:tt)* } let $pat1:pat | $($pat:pat)|+ = $expr:expr; $($tt:tt)+) => {
195        match $expr {
196            $pat1 | $($pat)|+ => { __if_chain! { @expand { $($other)* } $($tt)+ } }
197        }
198    };
199    // `if let` with a single pattern
200    (@expand {} if let $pat:pat = $expr:expr; $($tt:tt)+) => {
201        if let $pat = $expr {
202            __if_chain! { @expand {} $($tt)+ }
203        }
204    };
205    // `if let` with a single pattern and a fallback
206    (@expand { $($other:tt)+ } if let $pat:pat = $expr:expr; $($tt:tt)+) => {
207        if let $pat = $expr {
208            __if_chain! { @expand { $($other)+ } $($tt)+ }
209        } else {
210            $($other)+
211        }
212    };
213    // `if let` with multiple matterns and a fallback (if present)
214    (@expand { $($other:tt)* } if let $pat1:pat | $($pat:pat)|+ = $expr:expr; $($tt:tt)+) => {
215        match $expr {
216            $pat1 | $($pat)|+ => { __if_chain! { @expand { $($other)* } $($tt)+ } },
217            _ => { $($other)* }
218        }
219    };
220    // `if` with a successful case
221    (@expand {} if $expr:expr; $($tt:tt)+) => {
222        if $expr {
223            __if_chain! { @expand {} $($tt)+ }
224        }
225    };
226    // `if` with both a successful case and a fallback
227    (@expand { $($other:tt)+ } if $expr:expr; $($tt:tt)+) => {
228        if $expr {
229            __if_chain! { @expand { $($other)+ } $($tt)+ }
230        } else {
231            $($other)+
232        }
233    };
234    // Final macro call
235    (@expand { $($other:tt)* } then { $($then:tt)* }) => {
236        $($then)*
237    };
238}
239
240#[cfg(test)]
241mod tests {
242    #[test]
243    fn simple() {
244        let x: Option<Result<Option<String>, (u32, u32)>> = Some(Err((41, 42)));
245        let mut success = false;
246        if_chain! {
247            if let Some(y) = x;
248            if let Err(z) = y;
249            let (_, b) = z;
250            if b == 42;
251            then { success = true; }
252        }
253        assert!(success);
254    }
255
256    #[test]
257    fn empty() {
258        let success;
259        if_chain! {
260            then { success = true; }
261        }
262        assert!(success);
263    }
264
265    #[test]
266    fn empty_with_else() {
267        let success;
268        if_chain! {
269            then { success = true; }
270            else { unreachable!(); }
271        }
272        assert!(success);
273    }
274
275    #[test]
276    fn if_let_multiple_patterns() {
277        #[derive(Copy, Clone)]
278        enum Robot { Nano, Biscuit1, Biscuit2 }
279        for &(robot, expected) in &[
280            (Robot::Nano, false),
281            (Robot::Biscuit1, true),
282            (Robot::Biscuit2, true),
283        ] {
284            let is_biscuit = if_chain! {
285                if let Robot::Biscuit1 | Robot::Biscuit2 = robot;
286                then { true } else { false }
287            };
288            assert_eq!(is_biscuit, expected);
289        }
290    }
291
292    #[test]
293    fn let_multiple_patterns() {
294        let x: Result<u32, u32> = Ok(42);
295        if_chain! {
296            let Ok(x) | Err(x) = x;
297            then { assert_eq!(x, 42); }
298            else { panic!(); }
299        }
300    }
301
302    #[test]
303    fn let_type_annotation_patterns() {
304        let mut x = 1;
305        if_chain! {
306            if x > 0;
307            let y: u32 = 2;
308
309            then { x += y; }
310            else { x += 1; }
311        }
312        assert_eq!(x, 3);
313    }
314}