TL;DR:
-
if let
is a fancyif
condition. -
while let
is a fancywhile
loop.
They enter a codeblock if a destructuring assignment works.
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
.
A quick recap for array and tuple destructuring:
let arr = [1, 2, 3];
let [one, two, three] = arr;
println!("Counting: {}, {}, {}", one, two, three);
// Counting: 1, 2, 3
let tup = ("Daniel", "Ricciardo", 3);
let (first_name, last_name, racing_number) = tup;
println!("{} {} races with the number: {}", first_name, last_name, racing_number);
// Daniel Ricciardo races with the number: 3
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);
We could have also been even more specific and only execute the println!
if the number inside the Option
was 3, while ignoring any other possibility.
let my_pick: Option<u8> = Some(3);
let number = match my_pick {
Some(3) => 3,
_ => 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
— The Rust Programming Language bookif let
means less typing, less indentation, and less boilerplate code. However, you lose the exhaustive checking thatmatch
enforces
In the case we only care about my_pick
holding a Some(3)
:
let my_pick: Option<u8> = Some(3);
if let Some(3) = my_pick {
println!("Daniel Ricciardo, nicknamed 'the honeybadger' races with the number: 3");
}
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);
}
Like the regular while
loop,
while let
does not have an else if
or an else
case.
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!");
};
In the previous example, a straight comparison would fail because the enum doesn't implement the PartialEq
trait.
if SesameStreetChar::Count == best_vampire {
println!("this does not compile");
}
// error
if let
still works.
if let SesameStreetChar::Count = best_vampire {
println!("boo!");
}
Top comments (0)