It might help you to think of 'if let' as an extension of 'let' rather than an extension of 'if'. That is, 'let' by itself supports irrefutable patterns. e.g.,
let std::ops::Range { start, end } = 5..10;
So the 'if' is "just" allowing you to also write refutable patterns.
That is a useful way to think about it for sure, I’m mostly using it as an illustration of what is probably a philosophical difference between myself and the Rust maintainers; in other words, I don’t see why we need if let when we already have match with _ wildcards. It’s the sort of syntactic shortcut that gives authors of code relatively little benefit (save a few keystrokes) and readers of code yet one more syntactic variation to contend with.
I guess another way of putting it is that I think Rust has a lot of sugar that’s confusing.
Kotlin is an example of a language that has a lot of similar syntactic shortcuts and functional underpinnings that implements them in a more readable and consistent fashion imo.
I could live without 'if let'. I'm not a huge fan of it either, although I do use it.
Its most compelling benefit to me is not that it saves a few keystrokes, but that it avoids an extra indentation level. Compare (taking from a real example[1]):
if let Some(quits) = args.value_of_lossy("quit") {
for ch in quits.chars() {
if !ch.is_ascii() {
anyhow::bail!("quit bytes must be ASCII");
}
// FIXME(MSRV): use the 'TryFrom<char> for u8' impl once we are
// at Rust 1.59+.
c = c.quit(u8::try_from(u32::from(ch)).unwrap(), true);
}
}
with:
match args.value_of_lossy("quit") {
None => {}
Some(quits) => {
for ch in quits.chars() {
if !ch.is_ascii() {
anyhow::bail!("quit bytes must be ASCII");
}
// FIXME(MSRV): use the 'TryFrom<char> for u8' impl once we are
// at Rust 1.59+.
c = c.quit(u8::try_from(u32::from(ch)).unwrap(), true);
}
}
}
The 'for' loop is indented one extra level in the latter case. With that said, I do also use 'if let' because it saves some keystrokes. Taking from another real example[2], compare:
if let Some(name) = get_name(group_index) {
write!(buf, "/{}", name).unwrap();
}
(I could use '_ => {}' instead of 'None' to save a few more.)
I do find the 'if let' variant to be a bit easier to read. It's optimizing for a particular and somewhat common case, so it does of course overlap with 'match'. But I don't find this particular overlap to be too bad. It's usually pretty clear when to use one vs the other.
But like I said, I could live without 'if let'. It is not a major quality of life enhancement to me. Neither will its impending extensions. i.e., 'if let pattern = foo && some_booolean_condition {'.
I don't code in Rust--and thereby might be expected to not know all of these patterns and want to have to learn fewer bits--and yet I agree with you. I feel like removing this "if let" variant would be similar to saying we don't need if statements as they are equivalent to a loop that ends in break. I actually even will say the if let is much easier to read as with the match I have to check why it is a match and verify it has the None case--similar to checking if a loop is really going to loop or if it always ends in break--whereas I can skip all that work if I see the "if".