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