if let
For some use cases, when matching enums, match
is awkward. For example:
// Make `optional` of type `Option<i32>`
let optional = Some(7);
match optional {
Some(i) => {
println!("This is a really long string and `{:?}`", i);
// ^ Needed 2 indentations just so we could destructure
// `i` from the option.
},
_ => {},
// ^ Required because `match` is exhaustive. Doesn't it seem
// like wasted space?
};
if let
is cleaner for this use case and in addition allows various failure options to be specified:
fn main() {
// All have type `Option<i32>`
let number = Some(7);
let letter: Option<i32> = None;
let emoticon: Option<i32> = None;
// The `if let` construct reads: "if `let` destructures `number` into
// `Some(i)`, evaluate the block (`{}`).
if let Some(i) = number {
println!("Matched {:?}!", i);
}
// If you need to specify a failure, use an else:
if let Some(i) = letter {
println!("Matched {:?}!", i);
} else {
// Destructure failed. Change to the failure case.
println!("Didn't match a number. Let's go with a letter!");
}
// Provide an altered failing condition.
let i_like_letters = false;
if let Some(i) = emoticon {
println!("Matched {:?}!", i);
// Destructure failed. Evaluate an `else if` condition to see if the
// alternate failure branch should be taken:
} else if i_like_letters {
println!("Didn't match a number. Let's go with a letter!");
} else {
// The condition evaluated false. This branch is the default:
println!("I don't like letters. Let's go with an emoticon :)!");
}
}
In the same way, if let
can be used to match any enum value:
// Our example enum
enum Foo {
Bar,
Baz,
Qux(u32)
}
fn main() {
// Create example variables
let a = Foo::Bar;
let b = Foo::Baz;
let c = Foo::Qux(100);
// Variable a matches Foo::Bar
if let Foo::Bar = a {
println!("a is foobar");
}
// Variable b does not match Foo::Bar
// So this will print nothing
if let Foo::Bar = b {
println!("b is foobar");
}
// Variable c matches Foo::Qux which has a value
// Similar to Some() in the previous example
if let Foo::Qux(value) = c {
println!("c is {}", value);
}
// Binding also works with `if let`
if let Foo::Qux(value @ 100) = c {
println!("c is one hundred");
}
}
Another benefit is that if let
allows us to match non-parameterized enum variants. This is true even in cases where the enum doesn't implement or derive PartialEq
. In such cases if Foo::Bar == a
would fail to compile, because instances of the enum cannot be equated, however if let
will continue to work.
Would you like a challenge? Fix the following example to use if let
:
// This enum purposely neither implements nor derives PartialEq.
// That is why comparing Foo::Bar == a fails below.
enum Foo {Bar}
fn main() {
let a = Foo::Bar;
// Variable a matches Foo::Bar
if Foo::Bar == a {
// ^-- this causes a compile-time error. Use `if let` instead.
println!("a is foobar");
}
}