Coming from other functional languages (and being a Rust newbie), I'm a bit surprised by the motivation of Rust's if let
syntax. The RFC mentions that without if let
, the "idiomatic solution today for testing and unwrapping an Option<T>
" is either
match opt_val {
Some(x) => {
do_something_with(x);
}
None => {}
}
or
if opt_val.is_some() {
let x = opt_val.unwrap();
do_something_with(x);
}
In Scala, it would be possible to do exactly the same, but the idiomatic solution is rather to map
over an Option
(or to foreach
if it is only for the side effect of doing_something_with(x)
).
Why isn't it an idiomatic solution to do the same in Rust?
opt_val.map(|x| do_something_with(x));
.map()
is specific to the Option<T>
type, but if let
(and while let
!) are features that work with all Rust types.
map()
is intended for transforming an optional value, while if let
is mostly needed to perform side effects. While Rust is not a pure language, so any of its code blocks can contain side effects, map semantics is still there. Using map()
to perform side effects, while certainly possible, will only confuse readers of your code. Note that it should not have performance penalties, at least in simple code - LLVM optimizer is perfectly capable of inlining the closure directly into the calling function, so it turns to be equivalent to a match
statement.
Before if let
the only way to perform side effects on an Option
was either a match or if
with Option::is_some()
check. match
approach is the safest one, but it is very verbose, especially when a lot of nested checks are needed:
match o1 {
Some(v1) => match v1.f {
Some(v2) => match some_function(v2) {
Some(r) => ...
None => {}
}
None => {}
}
None => {}
}
Note the prominent rightward drift and a lot of syntactical noise. And it only gets worse if branches are not simple matches but proper blocks with multiple statements.
if option.is_some()
approach, on the other hand, is slightly less verbose but still reads very badly. Also its condition check and unwrap()
are not statically tied, so it is possible to get it wrong without the compiler noticing it.
if let
solves the verbosity problem, based on the same pattern matching infrastructure as match
(so it is harder to get wrong than if option.is_some()
) and, as a side benefit, allows using arbitrary types in patterns, not only Option
. For example, some types may not provide map()
-like methods; if let
will still work with them very nicely. So if let
is a clear win, hence it is idiomatic.
Because your solution creates a closure, which uses resources, whereas if let
desugars exactly to your first example, which doesn't. I also find it more readable.
Rust is all about zero-cost abstractions that make programming nicer, and if let
and while let
are good examples of those (at least IMO -- I realize it's a matter of personal preference). They're not strictly necessary, but they sure feel good to use (also see: Clojure, where they were likely lifted from).
A quote from a related issue against The Rust Programming Language:
You'd better think about if let
as a one-handed match
with no exhaustive checking enforced. It often has nothing to do with if
or let
, that's why it is so confusing. :)
Maybe it would be better to rename the whole operator, call it match once
or something like that.
(slightly modified to make sense without the context)