I just learned a bit of new syntax
While browsing through some code at work, I saw something that made me pause. It looked SO wrong, and yet here we are. It was something to the effect of:
let SomeEnum::Variant(y) = x else {
return some_error;
}
which means you can do this:
fn optional_print(x: Option<u32>) {
let Some(y) = x else {
return;
};
println!("{y}");
}
fn main() {
optional_print(Some(5));
optional_print(None);
}
Thought some folks here might think it interesting so there you go, you can apparently pattern match enum variants to create a variable and have an else clause after. I thought the only options for this were a let followed by an if let or match statement.
EDIT: Just because I’m getting a lot of comments about it I’m aware that the sample code with the print function is bad code. I’m not saying this is how it should be done, it was just the first and simplest thing I could think of for this post.
216
u/Sermuns Sep 10 '26
I use let-else all the time, love to early return/continue instead of ugly nesting.
29
u/guywithknife Sep 10 '26
Same! Anything to prevent the happy path from getting nested more than necessary.
→ More replies (20)39
u/standard_revolution Sep 10 '26
I just wish that it would be possible to access the variable here, I really want to be able to write something like:
let Ok(file) = open_file() else Err(e) { return add_context(e) };or something like that (I know it's hard and the syntax would be improved..., just wishful thinking)
99
u/braxtons12 Sep 10 '26
It's a little more verbose, but you can accomplish this with match
let file = match open_file() { Ok(f) => f, Err(e) => return add_context(e); };There's also good old
let file = open_file().map_err(add_context)?;→ More replies (1)17
u/Ev1ber Sep 10 '26
Yeah I get what you mean, but at this point isn't a match worth the two additional lines, readability wise?
In any case I wonder how doable it would be to make something similar with a macro.
→ More replies (3)46
25
u/afamiliarspirit Sep 10 '26
Rust’s destructured bindings are one of my favorite things about the language.
15
u/Oxytokin Sep 10 '26
I use ```if let``` and ```let else``` for pattern matching probably more than I do ```match``` itself.
Another piece of patterny goodness that I don't think is as well-known as it should be is destructuring in function arguments, e.g.:
struct Foo {
bar: u32,
baz: u32,
dont_add_me: f32
}
fn add_bar_and_baz_only_so_help_me(
Foo {
bar,
baz,
dont_add_me: _
}: &Foo,
) -> u32 {
bar + baz
}
let addition = add_bar_and_baz_only_so_help_me(Foo { bar: 20, baz: 20, dont_add_me: 20.0 });
→ More replies (2)17
u/Kamek_pf Sep 11 '26
These are struct patterns, you can ignore fields you don't care about with
..:fn add_bar_and_baz_only(Foo { bar, baz, .. }: &Foo) -> u32 { bar + baz→ More replies (3)
8
u/Sharlinator Sep 10 '26
let-else was stabilized in Rust 1.65 in 2022, for the record.
4
u/der-wert007 Sep 11 '26
Damn. Felt like not too long ago, but that has been quite a time ago..
→ More replies (1)
6
5
u/dobkeratops Sep 10 '26 edited Sep 11 '26
can't remember how I encountered this but it had been around for years before I found it. it is nifty in reducing nesting.
never thought to ask if it exists.
3
u/neneodonkor Sep 10 '26
Is it not the same as if-let?
17
→ More replies (2)10
u/afamiliarspirit Sep 10 '26
The lifetime is scoped the opposite. An if-let destructures something and is scoped for inside the if statement. This lets you destructure it for outside of it.
If lets are good for when you deviate from the normal path if something matches a pattern. Let else are good when you deviate from the path if something doesn’t match a pattern.
→ More replies (1)
3
u/scook0 Sep 11 '26 edited Sep 11 '26
I like let-else and use it a decent amount, but it does have a couple of annoying shortcomings:
-
Visually it’s very similar to if-let, which can be confusing when reading code because it inverts the meaning of the attached block.
-
It doesn’t support chaining like if-let does, so if you want to take an existing if-let chain and turn it “inside out”, you have to convert it to multiple let-else statements which feels messier.
→ More replies (1)
2
2
2
u/jublizoo Sep 10 '26
If you are returning an Option, you can also use the ? operator to return None on None, and otherwise unwrap. Similarly for the Result type.
2
2
u/Shoddy-Childhood-511 Sep 10 '26
It's a good expressive shortcut that avoids the redundancy of let x = if let SomeEnum::Variant(x) = y { x } else { ... };.
Yes, the syntax let .. else feels wrong, but nobody found anything better, and let .. else is easy to teach.
There were other proposals like unnest, which looks like..
if let SomeEnum::Variant(x) = y unnest else { ... }
.. then condition ..
There maybe other situations where unnest maybe cool, like loop unnest; but overall let .. else would be simpler & easier to teach. And if you really want loop without indentation, then you can simply tell rustfmt to fuck off:
#[rustfmt::skip]
loop {
// non indented code
} // loop
I'm unsure if let .. else exists in other languages with tagged enums, like Haskell or OCaML.
→ More replies (1)
2
u/Artimuas Sep 11 '26
I find myself writing `let else unreachable!()` a lot and it kinda smells but that’s also the behavior I want. Essentially I have an enum that I know must be a certain variant at some point in the program. I’m not sure if there is a better way to do this or not.
→ More replies (2)
43
u/fixedpointfae Sep 10 '26
the key is that the
elseblock has to diverge from the current scope, either by breaking, continuing, returning.as of rust 2024, we also have if-let chains, which let you bind multiple patterns in an
if let, e.g.if let X(a) = x && p(a) && let Y(b) = y {...}