Nime

Rust syntax: if let and while let

With the if let syntax you combine an if condition with a destructuring let assignment.

Let’s unpack (pun intended) that sentence.

The if condition

let my_pick: Option<u8> = Some(3);
if Some(3) == my_pick {
println!("Honeybadger don't care.");
}

This line prints, alerting everyone that Some(3) is equal to my_pick, and more importantly that honeybadgers don’t care.

Destructuring assignments

If array destructuring works with square brackets, and tuple destructuring works via parentheses, it makes sense an Option could be destructured via Some.

If you try to destructure what’s inside my_pick to the number variable with Some(number), Rust will not compile and show a warning:

let my_pick: Option<u8> = Some(3);
let Some(number) = my_pick;
println!("Daniel Ricciardo, nicknamed 'the honeybadger' races with the number: {}", number);
// error
error[E0005]: refutable pattern in local binding: `None` not covered

Because my_pick has the type of Option<u8>, it will either be a Some(<u8>) or a None. The piece of code above assumes there will always be some integer inside and neglects the possibility my_pick is None.

The Rust compiler won’t let you do that, it requires every possibile value for the Option to be covered!

In this contrived example, humans can see my_pick will always be Some(3), but the compiler can’t.

We can use a match to cover every possibility.

let my_pick: Option<u8> = Some(3);
let number = match my_pick {
Some(num) => num,
None => panic!("Oh no, no number!")
};
println!("Daniel Ricciardo, nicknamed 'the honeybadger' races with the number: {}", number);

That’s a bit verbose, we don’t care about the None case and only want to do something if my_pick holds a Some.

Even worse, the way this code is written, it depends on a Some being there, else it will panic. What we really want is to conditionally print that line. If my_pick is a Some: print it, if it is None: ignore that println and continue with the program.

Combining an if condition with a destructuring attempt

Combining that destructuring attempt with an if condition gives us the if let syntax.

It tries to destructure, if that destructure works, the attached codeblock is entered. If it doesn’t work, the codeblock isn’t entered and the code continues.

let my_pick: Option<u8> = Some(3);
if let Some(number) = my_pick {
println!("Daniel Ricciardo, nicknamed 'the honeybadger' races with the number: {}", number);
}

Using if let means less typing, less indentation, and less boilerplate code. However, you lose the exhaustive checking that match enforces

The else case

As you would expect from an if condition, it can be combined with else if and else. The else if condition can also be a conditional destructuring attempt with else if let.

let my_pick: Option<u8> = Some(33);
if let Some(3) = my_pick {
println!("Daniel Ricciardo, nicknamed 'the honeybadger' races with the number: 3");
} else if let Some(33) = my_pick {
println!("Max, Max, Max, super Max, Max");
} else {
println!("Unknown driver");
}

Here, my_pick is Some(33). The if doesn’t succeed anymore, the else if condition succeeds, printing a lyric from a Max Verstappen fansong.

If my_pick held yet another Some or if it held None, the else block would execute.

Do it again, and again

The while let syntax does almost the same thing. Instead of continuing on when the end of the conditional codeblock is reached, it checks the condition again. If the destructuring that is attempted there works again, enter the codeblock again! Rinse, repeat.

The pop method on a vector returns an Option. The code below uses while let to print out the number if pop returns a Some with that number. In the case where pop returns None, the codeblock will not be entered again and the loop stops.

let mut my_picks: Vec<u8> = vec![3, 33, 44];
while let Some(num) = my_picks.pop() {
println!("{}", num);
}

Not limited to the Option type

The if let and while let doesn’t always need to try and destructure an Option.

The following piece of code tries to destructure an Ok(3) from a Result.

let my_pick: Result<u8, ()> = Ok(3);
if let Ok(3) = my_pick {
println!("Daniel Ricciardo, nicknamed 'the honeybadger' races with the number: 3");
}

The thing we try to destructure doesn’t have to be the kind of enum variant that holds something, simple enums variants work too!

enum SesameStreetChar {
Count,
// others
}
let best_vampire = SesameStreetChar::Count;
if let SesameStreetChar::Count = best_vampire {
println!("Ah, ah, ah!");
};