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 thatmatch
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!");};