-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: let-else statements #3137
RFC: let-else statements #3137
Conversation
Specifically bstrie's suggestion of `ExpressionWithoutBlock`. Also some spelling fixes and example updates.
Thanks to Frank Steffahn for pointing this out in Zulip.
By request of Josh Triplett
Also mentions the diverging return type of `!` in the summary.
Co-Authored-By: Josh Triplett <[email protected]>
It's be more readable if |
The example if let Some(a) = x {
if let Some(b) = y {
if let Some(c) = z {
// ...
do_something_with(a, b, c);
// ...
} else {
return Err("bad z");
}
} else {
return Err("bad y");
}
} else {
return Err("bad x");
} can be written as match (x, y, z) {
((Some(a), Some(b), Some(c)) => do_something_with(a, b, c),
((Some(a), Some(b), _)) => return Err("bad z"),
((Some(a), _ , _)) => return Err("bad y"),
_ => return Err("bad x"),
} which doesn't seem bad comparing to the proposed let Some(a) = x else {
return Err("bad x");
};
let Some(b) = y else {
return Err("bad y");
};
let Some(c) = z else {
return Err("bad z");
};
// ...
do_something_with(a, b, c); The real-world example can just use
|
You meant
but simpler competition exists, like
If you're doing anything with
or
I'd say remove
In reality, this shortcut would exist exclusively for custom enums and should probably never be used with anything from std. |
@burdges
If there's one refutable pattern to match, then |
That basic example may need to be improved a bit. Often times in practical use there is other code between the previous and next if statement, which cannot be represented by this
It can in fact add practical utility. Please see the practical refactor section of the RFC.
This is already covered in the RFC in more detail. See the alternatives section which notes that |
Also consider the following case enum Fruit {
Apple(u32),
Banana(u32),
}
struct AppleJuice;
fn make_apple_juice(fruit: &Fruit) -> Result<AppleJuice, String> {
match fruit {
Fruit::Apple(v) => Ok(AppleJuice),
Fruit::Banana(v) => Err(String::from("Require apple, found %s bananas", v)),
}
} This cannot be written with the proposed |
Enums with multiple value-bearing variants do not necessarily need or want other variant values to be accessed in case of a failed match. The examples section does cover this. Also if it helps, the |
This refactor is not a fair comparison. let GeoJson::FeatureCollection(features) = geojson else {
return Err(format_err_status!(
422,
"GeoJSON was not a Feature Collection",
));
}; any better than let features = if let GeoJson::FeatureCollection(features) = geojson {
features
} else {
return Err(format_err_status!(
422,
"GeoJSON was not a Feature Collection",
));
}; It merely removed two lines. Does this justify adding the new constuct |
let x = if let Pattern(x) = y { x } else {
// return or something
}; Any real-world code I've seen uses That being said, yes I do think the few lines of elimination bring enhanced clarity and newcomer discoverability, especially for something which can be pretty common, and which desugars into something pretty simple without much complexity. Quoting myself from the RFC:
|
@lebensterben wrote:
This is assuming that the actual expressions involved in y and z (which are examples) don't involve any previous bindings. If |
The best use case for let-else is probably something like the following (where foo and bar are functions with side effects) let Some(a) = foo() else {
log("Bad foo");
return Err("foo failed");
};
let Some(b) = bar() else {
log("Bad bar");
return Err("bar failed");
}; This example can also be rewritten using if-else or map_err()? or two separate match statements, but admittedly the version with let-else is slightly prettier. |
I think only the word |
Should this be the case? From C# experience we know that it may be used like this: if (new object() is not int x) {
x = 10;
}
Console.WriteLine(x); // prints 10 So if value is NOT matched then if block gets executed. I think that it coul be applied here as well: instead of returning/breaking/... it may actually set the variable. You may do this with |
The C# sample is closer to a match block (indeed, that's referred to as pattern matching in C#). if (new object() is not int x) {
x = 10;
}
Console.WriteLine(x); // prints 10 is equivalent to rust's
(Except it's not an The important bit is that commenting out |
Nope, it's not the equivalent. Your rust code is equivalent to var x = (new object()) switch {
int x => x,
_ => 10
}; But it's not what I've written. This code creates a scope (and indentation) which is what we're trying to avoid here. Consider that instead of
Right, this is what I meant, but I missed this, thanks. Rust shouldn't require block do diverge but it should treat binding as unassigned instead (because well it is), and that's it |
@Pzixel I wasn't disagreeing with you and I wasn't posting an equivalent developer UX but rather an equivalent compilation to clarify the behavior for those not familiar with C#. Especially the bit about C# not letting you just escape the typeguard willy-nilly, for those that don't/didn't realize how strictly typed C# was in that regard. |
The approach with let x: Result<usize, usize> = Err(2);
let Ok(x) = x else {
x = x.unwrap_err(); // error: using uninitialized variable `x: usize`
}; |
I'm not sure how is it misleading. It works just in the same way: if (default(int[]) is not { Length: 5, LongLength: var y } x)
{
x = new int[] { 10 };
y = 1;
}
Console.WriteLine(x);
Console.WriteLine(y); Is variable was not set it's just unbinded and causes In your example you won't be able to use the shadowed |
What does this do that EDIT: Uhh, okay, it doesn't create another level of nesting. |
What is misleading is that there are two somewhat reasonable options for the binding structure, and people will have different predispositions to one or the other, and if they stumble on some code like this without having read the RFC carefully they might be mislead. Personally, I am predisposed to expect that a variable should not be in scope in a different branch from where it is introduced - nothing else in rust is like that - so in The idea behind forced divergence here is that you don't have to make a choice - you don't need to know what happens after the block completes, where it goes and what values are assigned - because it never gets there and we ensure this is always the case. The situation with using the shadowed variable seems rather likely to me, for example: let Foo::Bar(x) = x else { panic!("unexpected: {:?}", x) }; I'm using only rust examples here, not C#, because I think the intuitions might differ, since unboxing has a significantly different syntactic expression, |
C# has (un)boxing and stuff, right, but this is beside the point. What I'm saying that assigning variable that wasn't bind may be useful. Of course if you want to do so you need to assign this to another name otherwise the old one would be shadowed. I think both approaches has pros and cons, and I'm sharing my usage examples to help making proper decision.
This is also true but also beside the point: C# doesn't have shadowing of locals but we still can imagine how would it work in Rust. For example: let Foo::Bar(x) = x else { panic!("unexpected: {:?}", x) }; // is not allowed: x is not assigned
let Foo::Bar(y) = x else { panic!("unexpected: {:?}", x) }; // ok
let Foo::Bar(y) = x else { y = 15 }; // ok |
Please see the fallback assignment part of the RFC discussed under rationale and also mentioned in future possibilities: |
Replaces `guard::guard!` with the native `if let ... else` from <rust-lang/rfcs#3137> (still unstable).
…plett Stabilize `let else` :tada: **Stabilizes the `let else` feature, added by [RFC 3137](rust-lang/rfcs#3137 🎉 Reference PR: rust-lang/reference#1156 closes rust-lang#87335 (`let else` tracking issue) FCP: rust-lang#93628 (comment) ---------- ## Stabilization report ### Summary The feature allows refutable patterns in `let` statements if the expression is followed by a diverging `else`: ```Rust fn get_count_item(s: &str) -> (u64, &str) { let mut it = s.split(' '); let (Some(count_str), Some(item)) = (it.next(), it.next()) else { panic!("Can't segment count item pair: '{s}'"); }; let Ok(count) = u64::from_str(count_str) else { panic!("Can't parse integer: '{count_str}'"); }; (count, item) } assert_eq!(get_count_item("3 chairs"), (3, "chairs")); ``` ### Differences from the RFC / Desugaring Outside of desugaring I'm not aware of any differences between the implementation and the RFC. The chosen desugaring has been changed from the RFC's [original](https://rust-lang.github.io/rfcs/3137-let-else.html#reference-level-explanations). You can read a detailed discussion of the implementation history of it in `@cormacrelf` 's [summary](rust-lang#93628 (comment)) in this thread, as well as the [followup](rust-lang#93628 (comment)). Since that followup, further changes have happened to the desugaring, in rust-lang#98574, rust-lang#99518, rust-lang#99954. The later changes were mostly about the drop order: On match, temporaries drop in the same order as they would for a `let` declaration. On mismatch, temporaries drop before the `else` block. ### Test cases In chronological order as they were merged. Added by df9a2e0 (rust-lang#87688): * [`ui/pattern/usefulness/top-level-alternation.rs`](https://github.com/rust-lang/rust/blob/1.58.1/src/test/ui/pattern/usefulness/top-level-alternation.rs) to ensure the unreachable pattern lint visits patterns inside `let else`. Added by 5b95df4 (rust-lang#87688): * [`ui/let-else/let-else-bool-binop-init.rs`](https://github.com/rust-lang/rust/blob/1.58.1/src/test/ui/let-else/let-else-bool-binop-init.rs) to ensure that no lazy boolean expressions (using `&&` or `||`) are allowed in the expression, as the RFC mandates. * [`ui/let-else/let-else-brace-before-else.rs`](https://github.com/rust-lang/rust/blob/1.58.1/src/test/ui/let-else/let-else-brace-before-else.rs) to ensure that no `}` directly preceding the `else` is allowed in the expression, as the RFC mandates. * [`ui/let-else/let-else-check.rs`](https://github.com/rust-lang/rust/blob/1.58.1/src/test/ui/let-else/let-else-check.rs) to ensure that `#[allow(...)]` attributes added to the entire `let` statement apply for the `else` block. * [`ui/let-else/let-else-irrefutable.rs`](https://github.com/rust-lang/rust/blob/1.58.1/src/test/ui/let-else/let-else-irrefutable.rs) to ensure that the `irrefutable_let_patterns` lint fires. * [`ui/let-else/let-else-missing-semicolon.rs`](https://github.com/rust-lang/rust/blob/1.58.1/src/test/ui/let-else/let-else-missing-semicolon.rs) to ensure the presence of semicolons at the end of the `let` statement. * [`ui/let-else/let-else-non-diverging.rs`](https://github.com/rust-lang/rust/blob/1.58.1/src/test/ui/let-else/let-else-non-diverging.rs) to ensure the `else` block diverges. * [`ui/let-else/let-else-run-pass.rs`](https://github.com/rust-lang/rust/blob/1.58.1/src/test/ui/let-else/let-else-run-pass.rs) to ensure the feature works in some simple test case settings. * [`ui/let-else/let-else-scope.rs`](https://github.com/rust-lang/rust/blob/1.58.1/src/test/ui/let-else/let-else-scope.rs) to ensure the bindings created by the outer `let` expression are not available in the `else` block of it. Added by bf7c32a (rust-lang#89965): * [`ui/let-else/issue-89960.rs`](https://github.com/rust-lang/rust/blob/1.58.1/src/test/ui/let-else/issue-89960.rs) as a regression test for the ICE-on-error bug rust-lang#89960 . Later in 102b912 this got removed in favour of more comprehensive tests. Added by 8565419 (rust-lang#89974): * [`ui/let-else/let-else-if.rs`](https://github.com/rust-lang/rust/blob/1.58.1/src/test/ui/let-else/let-else-if.rs) to test for the improved error message that points out that `let else if` is not possible. Added by 9b45713: * [`ui/let-else/let-else-allow-unused.rs`](https://github.com/rust-lang/rust/blob/1.61.0/src/test/ui/let-else/let-else-allow-unused.rs) as a regression test for rust-lang#89807, to ensure that `#[allow(...)]` attributes added to the entire `let` statement apply for bindings created by the `let else` pattern. Added by 61bcd8d (rust-lang#89841): * [`ui/let-else/let-else-non-copy.rs`](https://github.com/rust-lang/rust/blob/1.61.0/src/test/ui/let-else/let-else-non-copy.rs) to ensure that a copy is performed out of non-copy wrapper types. This mirrors `if let` behaviour. The test case bases on rustc internal changes originally meant for rust-lang#89933 but then removed from the PR due to the error prior to the improvements of rust-lang#89841. * [`ui/let-else/let-else-source-expr-nomove-pass.rs `](https://github.com/rust-lang/rust/blob/1.61.0/src/test/ui/let-else/let-else-source-expr-nomove-pass.rs) to ensure that while there is a move of the binding in the successful case, the `else` case can still access the non-matching value. This mirrors `if let` behaviour. Added by 102b912 (rust-lang#89841): * [`ui/let-else/let-else-ref-bindings.rs`](https://github.com/rust-lang/rust/blob/1.61.0/src/test/ui/let-else/let-else-ref-bindings.rs) and [`ui/let-else/let-else-ref-bindings-pass.rs `](https://github.com/rust-lang/rust/blob/1.61.0/src/test/ui/let-else/let-else-ref-bindings-pass.rs) to check `ref` and `ref mut` keywords in the pattern work correctly and error when needed. Added by 2715c5f (rust-lang#89841): * Match ergonomic tests adapted from the `rfc2005` test suite. Added by fec8a50 (rust-lang#89841): * [`ui/let-else/let-else-deref-coercion-annotated.rs`](https://github.com/rust-lang/rust/blob/1.61.0/src/test/ui/let-else/let-else-deref-coercion-annotated.rs) and [`ui/let-else/let-else-deref-coercion.rs`](https://github.com/rust-lang/rust/blob/1.61.0/src/test/ui/let-else/let-else-deref-coercion.rs) to check deref coercions. #### Added since this stabilization report was originally written (2022-02-09) Added by 76ea566 (rust-lang#94211): * [`ui/let-else/let-else-destructuring.rs`](https://github.com/rust-lang/rust/blob/1.63.0/src/test/ui/let-else/let-else-destructuring.rs) to give a nice error message if an user tries to do an assignment with a (possibly refutable) pattern and an `else` block, like asked for in rust-lang#93995. Added by e7730dc (rust-lang#94208): * [`ui/let-else/let-else-allow-in-expr.rs`](https://github.com/rust-lang/rust/blob/1.61.0/src/test/ui/let-else/let-else-allow-in-expr.rs) to test whether `#[allow(unused_variables)]` works in the expr, as well as its non presence, as well as putting it on the entire `let else` *affects* the expr, too. This was adding a missing test as pointed out by the stabilization report. * Expansion of `ui/let-else/let-else-allow-unused.rs` and `ui/let-else/let-else-check.rs` to ensure that non-presence of `#[allow(unused)]` does issue the unused lint. This was adding a missing test case as pointed out by the stabilization report. Added by 5bd7106 (rust-lang#94208): * [`ui/let-else/let-else-slicing-error.rs`](https://github.com/rust-lang/rust/blob/1.61.0/src/test/ui/let-else/let-else-slicing-error.rs), a regression test for rust-lang#92069, which got fixed without addition of a regression test. This resolves a missing test as pointed out by the stabilization report. Added by 5374688 (rust-lang#98574): * [`src/test/ui/async-await/async-await-let-else.rs`](https://github.com/rust-lang/rust/blob/master/src/test/ui/async-await/async-await-let-else.rs) to test the interaction of async/await with `let else` Added by 6c529de (rust-lang#98574): * [`src/test/ui/let-else/let-else-temporary-lifetime.rs`](https://github.com/rust-lang/rust/blob/master/src/test/ui/let-else/let-else-temporary-lifetime.rs) as a (partial) regression test for rust-lang#98672 Added by 9b56640 (rust-lang#99518): * [`src/test/ui/let-else/let-else-temp-borrowck.rs`](https://github.com/rust-lang/rust/blob/master/src/test/ui/let-else/let-else-temporary-lifetime.rs) as a regression test for rust-lang#93951 * Extension of `src/test/ui/let-else/let-else-temporary-lifetime.rs` to include a partial regression test for rust-lang#98672 (especially regarding `else` drop order) Added by baf9a7c (rust-lang#99518): * Extension of `src/test/ui/let-else/let-else-temporary-lifetime.rs` to include a partial regression test for rust-lang#93951, similar to `let-else-temp-borrowck.rs` Added by 60be2de (rust-lang#99518): * Extension of `src/test/ui/let-else/let-else-temporary-lifetime.rs` to include a program that can now be compiled thanks to borrow checker implications of rust-lang#99518 Added by 47a7a91 (rust-lang#100132): * [`src/test/ui/let-else/issue-100103.rs`](https://github.com/rust-lang/rust/blob/master/src/test/ui/let-else/issue-100103.rs), as a regression test for rust-lang#100103, to ensure that there is no ICE when doing `Err(...)?` inside else blocks. Added by e3c5bd6 (rust-lang#100443): * [`src/test/ui/let-else/let-else-then-diverge.rs`](https://github.com/rust-lang/rust/blob/master/src/test/ui/let-else/let-else-then-diverge.rs), to verify that there is no unreachable code error with the current desugaring. Added by 9818526 (rust-lang#100443): * [`src/test/ui/let-else/issue-94176.rs`](https://github.com/rust-lang/rust/blob/master/src/test/ui/let-else/issue-94176.rs), to make sure that a correct span is emitted for a missing trailing expression error. Regression test for rust-lang#94176. Added by e182d12 (rust-lang#100434): * [src/test/ui/unpretty/pretty-let-else.rs](https://github.com/rust-lang/rust/blob/master/src/test/ui/unpretty/pretty-let-else.rs), as a regression test to ensure pretty printing works for `let else` (this bug surfaced in many different ways) Added by e262856 (rust-lang#99954): * [`src/test/ui/let-else/let-else-temporary-lifetime.rs`](https://github.com/rust-lang/rust/blob/master/src/test/ui/let-else/let-else-temporary-lifetime.rs) extended to contain & borrows as well, as this was identified as an earlier issue with the desugaring: rust-lang#98672 (comment) Added by 2d8460e (rust-lang#99291): * [`src/test/ui/let-else/let-else-drop-order.rs`](https://github.com/rust-lang/rust/blob/master/src/test/ui/let-else/let-else-drop-order.rs) a matrix based test for various drop order behaviour of `let else`. Especially, it verifies equality of `let` and `let else` drop orders, [resolving](rust-lang#93628 (comment)) a [stabilization blocker](rust-lang#93628 (comment)). Added by 1b87ce0 (rust-lang#101410): * Edit to `src/test/ui/let-else/let-else-temporary-lifetime.rs` to add the `-Zvalidate-mir` flag, as a regression test for rust-lang#99228 Added by af591eb (rust-lang#101410): * [`src/test/ui/let-else/issue-99975.rs`](https://github.com/rust-lang/rust/blob/master/src/test/ui/let-else/issue-99975.rs) as a regression test for the ICE rust-lang#99975. Added by this PR: * `ui/let-else/let-else.rs`, a simple run-pass check, similar to `ui/let-else/let-else-run-pass.rs`. ### Things not currently tested * ~~The `#[allow(...)]` tests check whether allow works, but they don't check whether the non-presence of allow causes a lint to fire.~~ → *test added by e7730dc* * ~~There is no `#[allow(...)]` test for the expression, as there are tests for the pattern and the else block.~~ → *test added by e7730dc* * ~~`let-else-brace-before-else.rs` forbids the `let ... = {} else {}` pattern and there is a rustfix to obtain `let ... = ({}) else {}`. I'm not sure whether the `.fixed` files are checked by the tooling that they compile. But if there is no such check, it would be neat to make sure that `let ... = ({}) else {}` compiles.~~ → *test added by e7730dc* * ~~rust-lang#92069 got closed as fixed, but no regression test was added. Not sure it's worth to add one.~~ → *test added by 5bd7106* * ~~consistency between `let else` and `if let` regarding lifetimes and drop order: rust-lang#93628 (comment) → *test added by 2d8460e* Edit: they are all tested now. ### Possible future work / Refutable destructuring assignments [RFC 2909](https://rust-lang.github.io/rfcs/2909-destructuring-assignment.html) specifies destructuring assignment, allowing statements like `FooBar { a, b, c } = foo();`. As it was stabilized, destructuring assignment only allows *irrefutable* patterns, which before the advent of `let else` were the only patterns that `let` supported. So the combination of `let else` and destructuring assignments gives reason to think about extensions of the destructuring assignments feature that allow refutable patterns, discussed in rust-lang#93995. A naive mapping of `let else` to destructuring assignments in the form of `Some(v) = foo() else { ... };` might not be the ideal way. `let else` needs a diverging `else` clause as it introduces new bindings, while assignments have a default behaviour to fall back to if the pattern does not match, in the form of not performing the assignment. Thus, there is no good case to require divergence, or even an `else` clause at all, beyond the need for having *some* introducer syntax so that it is clear to readers that the assignment is not a given (enums and structs look similar). There are better candidates for introducer syntax however than an empty `else {}` clause, like `maybe` which could be added as a keyword on an edition boundary: ```Rust let mut v = 0; maybe Some(v) = foo(&v); maybe Some(v) = foo(&v) else { bar() }; ``` Further design discussion is left to an RFC, or the linked issue.
…plett Stabilize `let else` :tada: **Stabilizes the `let else` feature, added by [RFC 3137](rust-lang/rfcs#3137 🎉 Reference PR: rust-lang/reference#1156 closes rust-lang#87335 (`let else` tracking issue) FCP: rust-lang#93628 (comment) ---------- ## Stabilization report ### Summary The feature allows refutable patterns in `let` statements if the expression is followed by a diverging `else`: ```Rust fn get_count_item(s: &str) -> (u64, &str) { let mut it = s.split(' '); let (Some(count_str), Some(item)) = (it.next(), it.next()) else { panic!("Can't segment count item pair: '{s}'"); }; let Ok(count) = u64::from_str(count_str) else { panic!("Can't parse integer: '{count_str}'"); }; (count, item) } assert_eq!(get_count_item("3 chairs"), (3, "chairs")); ``` ### Differences from the RFC / Desugaring Outside of desugaring I'm not aware of any differences between the implementation and the RFC. The chosen desugaring has been changed from the RFC's [original](https://rust-lang.github.io/rfcs/3137-let-else.html#reference-level-explanations). You can read a detailed discussion of the implementation history of it in `@cormacrelf` 's [summary](rust-lang#93628 (comment)) in this thread, as well as the [followup](rust-lang#93628 (comment)). Since that followup, further changes have happened to the desugaring, in rust-lang#98574, rust-lang#99518, rust-lang#99954. The later changes were mostly about the drop order: On match, temporaries drop in the same order as they would for a `let` declaration. On mismatch, temporaries drop before the `else` block. ### Test cases In chronological order as they were merged. Added by df9a2e0 (rust-lang#87688): * [`ui/pattern/usefulness/top-level-alternation.rs`](https://github.com/rust-lang/rust/blob/1.58.1/src/test/ui/pattern/usefulness/top-level-alternation.rs) to ensure the unreachable pattern lint visits patterns inside `let else`. Added by 5b95df4 (rust-lang#87688): * [`ui/let-else/let-else-bool-binop-init.rs`](https://github.com/rust-lang/rust/blob/1.58.1/src/test/ui/let-else/let-else-bool-binop-init.rs) to ensure that no lazy boolean expressions (using `&&` or `||`) are allowed in the expression, as the RFC mandates. * [`ui/let-else/let-else-brace-before-else.rs`](https://github.com/rust-lang/rust/blob/1.58.1/src/test/ui/let-else/let-else-brace-before-else.rs) to ensure that no `}` directly preceding the `else` is allowed in the expression, as the RFC mandates. * [`ui/let-else/let-else-check.rs`](https://github.com/rust-lang/rust/blob/1.58.1/src/test/ui/let-else/let-else-check.rs) to ensure that `#[allow(...)]` attributes added to the entire `let` statement apply for the `else` block. * [`ui/let-else/let-else-irrefutable.rs`](https://github.com/rust-lang/rust/blob/1.58.1/src/test/ui/let-else/let-else-irrefutable.rs) to ensure that the `irrefutable_let_patterns` lint fires. * [`ui/let-else/let-else-missing-semicolon.rs`](https://github.com/rust-lang/rust/blob/1.58.1/src/test/ui/let-else/let-else-missing-semicolon.rs) to ensure the presence of semicolons at the end of the `let` statement. * [`ui/let-else/let-else-non-diverging.rs`](https://github.com/rust-lang/rust/blob/1.58.1/src/test/ui/let-else/let-else-non-diverging.rs) to ensure the `else` block diverges. * [`ui/let-else/let-else-run-pass.rs`](https://github.com/rust-lang/rust/blob/1.58.1/src/test/ui/let-else/let-else-run-pass.rs) to ensure the feature works in some simple test case settings. * [`ui/let-else/let-else-scope.rs`](https://github.com/rust-lang/rust/blob/1.58.1/src/test/ui/let-else/let-else-scope.rs) to ensure the bindings created by the outer `let` expression are not available in the `else` block of it. Added by bf7c32a (rust-lang#89965): * [`ui/let-else/issue-89960.rs`](https://github.com/rust-lang/rust/blob/1.58.1/src/test/ui/let-else/issue-89960.rs) as a regression test for the ICE-on-error bug rust-lang#89960 . Later in 102b912 this got removed in favour of more comprehensive tests. Added by 8565419 (rust-lang#89974): * [`ui/let-else/let-else-if.rs`](https://github.com/rust-lang/rust/blob/1.58.1/src/test/ui/let-else/let-else-if.rs) to test for the improved error message that points out that `let else if` is not possible. Added by 9b45713: * [`ui/let-else/let-else-allow-unused.rs`](https://github.com/rust-lang/rust/blob/1.61.0/src/test/ui/let-else/let-else-allow-unused.rs) as a regression test for rust-lang#89807, to ensure that `#[allow(...)]` attributes added to the entire `let` statement apply for bindings created by the `let else` pattern. Added by 61bcd8d (rust-lang#89841): * [`ui/let-else/let-else-non-copy.rs`](https://github.com/rust-lang/rust/blob/1.61.0/src/test/ui/let-else/let-else-non-copy.rs) to ensure that a copy is performed out of non-copy wrapper types. This mirrors `if let` behaviour. The test case bases on rustc internal changes originally meant for rust-lang#89933 but then removed from the PR due to the error prior to the improvements of rust-lang#89841. * [`ui/let-else/let-else-source-expr-nomove-pass.rs `](https://github.com/rust-lang/rust/blob/1.61.0/src/test/ui/let-else/let-else-source-expr-nomove-pass.rs) to ensure that while there is a move of the binding in the successful case, the `else` case can still access the non-matching value. This mirrors `if let` behaviour. Added by 102b912 (rust-lang#89841): * [`ui/let-else/let-else-ref-bindings.rs`](https://github.com/rust-lang/rust/blob/1.61.0/src/test/ui/let-else/let-else-ref-bindings.rs) and [`ui/let-else/let-else-ref-bindings-pass.rs `](https://github.com/rust-lang/rust/blob/1.61.0/src/test/ui/let-else/let-else-ref-bindings-pass.rs) to check `ref` and `ref mut` keywords in the pattern work correctly and error when needed. Added by 2715c5f (rust-lang#89841): * Match ergonomic tests adapted from the `rfc2005` test suite. Added by fec8a50 (rust-lang#89841): * [`ui/let-else/let-else-deref-coercion-annotated.rs`](https://github.com/rust-lang/rust/blob/1.61.0/src/test/ui/let-else/let-else-deref-coercion-annotated.rs) and [`ui/let-else/let-else-deref-coercion.rs`](https://github.com/rust-lang/rust/blob/1.61.0/src/test/ui/let-else/let-else-deref-coercion.rs) to check deref coercions. #### Added since this stabilization report was originally written (2022-02-09) Added by 76ea566 (rust-lang#94211): * [`ui/let-else/let-else-destructuring.rs`](https://github.com/rust-lang/rust/blob/1.63.0/src/test/ui/let-else/let-else-destructuring.rs) to give a nice error message if an user tries to do an assignment with a (possibly refutable) pattern and an `else` block, like asked for in rust-lang#93995. Added by e7730dc (rust-lang#94208): * [`ui/let-else/let-else-allow-in-expr.rs`](https://github.com/rust-lang/rust/blob/1.61.0/src/test/ui/let-else/let-else-allow-in-expr.rs) to test whether `#[allow(unused_variables)]` works in the expr, as well as its non presence, as well as putting it on the entire `let else` *affects* the expr, too. This was adding a missing test as pointed out by the stabilization report. * Expansion of `ui/let-else/let-else-allow-unused.rs` and `ui/let-else/let-else-check.rs` to ensure that non-presence of `#[allow(unused)]` does issue the unused lint. This was adding a missing test case as pointed out by the stabilization report. Added by 5bd7106 (rust-lang#94208): * [`ui/let-else/let-else-slicing-error.rs`](https://github.com/rust-lang/rust/blob/1.61.0/src/test/ui/let-else/let-else-slicing-error.rs), a regression test for rust-lang#92069, which got fixed without addition of a regression test. This resolves a missing test as pointed out by the stabilization report. Added by 5374688 (rust-lang#98574): * [`src/test/ui/async-await/async-await-let-else.rs`](https://github.com/rust-lang/rust/blob/master/src/test/ui/async-await/async-await-let-else.rs) to test the interaction of async/await with `let else` Added by 6c529de (rust-lang#98574): * [`src/test/ui/let-else/let-else-temporary-lifetime.rs`](https://github.com/rust-lang/rust/blob/master/src/test/ui/let-else/let-else-temporary-lifetime.rs) as a (partial) regression test for rust-lang#98672 Added by 9b56640 (rust-lang#99518): * [`src/test/ui/let-else/let-else-temp-borrowck.rs`](https://github.com/rust-lang/rust/blob/master/src/test/ui/let-else/let-else-temporary-lifetime.rs) as a regression test for rust-lang#93951 * Extension of `src/test/ui/let-else/let-else-temporary-lifetime.rs` to include a partial regression test for rust-lang#98672 (especially regarding `else` drop order) Added by baf9a7c (rust-lang#99518): * Extension of `src/test/ui/let-else/let-else-temporary-lifetime.rs` to include a partial regression test for rust-lang#93951, similar to `let-else-temp-borrowck.rs` Added by 60be2de (rust-lang#99518): * Extension of `src/test/ui/let-else/let-else-temporary-lifetime.rs` to include a program that can now be compiled thanks to borrow checker implications of rust-lang#99518 Added by 47a7a91 (rust-lang#100132): * [`src/test/ui/let-else/issue-100103.rs`](https://github.com/rust-lang/rust/blob/master/src/test/ui/let-else/issue-100103.rs), as a regression test for rust-lang#100103, to ensure that there is no ICE when doing `Err(...)?` inside else blocks. Added by e3c5bd6 (rust-lang#100443): * [`src/test/ui/let-else/let-else-then-diverge.rs`](https://github.com/rust-lang/rust/blob/master/src/test/ui/let-else/let-else-then-diverge.rs), to verify that there is no unreachable code error with the current desugaring. Added by 9818526 (rust-lang#100443): * [`src/test/ui/let-else/issue-94176.rs`](https://github.com/rust-lang/rust/blob/master/src/test/ui/let-else/issue-94176.rs), to make sure that a correct span is emitted for a missing trailing expression error. Regression test for rust-lang#94176. Added by e182d12 (rust-lang#100434): * [src/test/ui/unpretty/pretty-let-else.rs](https://github.com/rust-lang/rust/blob/master/src/test/ui/unpretty/pretty-let-else.rs), as a regression test to ensure pretty printing works for `let else` (this bug surfaced in many different ways) Added by e262856 (rust-lang#99954): * [`src/test/ui/let-else/let-else-temporary-lifetime.rs`](https://github.com/rust-lang/rust/blob/master/src/test/ui/let-else/let-else-temporary-lifetime.rs) extended to contain & borrows as well, as this was identified as an earlier issue with the desugaring: rust-lang#98672 (comment) Added by 2d8460e (rust-lang#99291): * [`src/test/ui/let-else/let-else-drop-order.rs`](https://github.com/rust-lang/rust/blob/master/src/test/ui/let-else/let-else-drop-order.rs) a matrix based test for various drop order behaviour of `let else`. Especially, it verifies equality of `let` and `let else` drop orders, [resolving](rust-lang#93628 (comment)) a [stabilization blocker](rust-lang#93628 (comment)). Added by 1b87ce0 (rust-lang#101410): * Edit to `src/test/ui/let-else/let-else-temporary-lifetime.rs` to add the `-Zvalidate-mir` flag, as a regression test for rust-lang#99228 Added by af591eb (rust-lang#101410): * [`src/test/ui/let-else/issue-99975.rs`](https://github.com/rust-lang/rust/blob/master/src/test/ui/let-else/issue-99975.rs) as a regression test for the ICE rust-lang#99975. Added by this PR: * `ui/let-else/let-else.rs`, a simple run-pass check, similar to `ui/let-else/let-else-run-pass.rs`. ### Things not currently tested * ~~The `#[allow(...)]` tests check whether allow works, but they don't check whether the non-presence of allow causes a lint to fire.~~ → *test added by e7730dc* * ~~There is no `#[allow(...)]` test for the expression, as there are tests for the pattern and the else block.~~ → *test added by e7730dc* * ~~`let-else-brace-before-else.rs` forbids the `let ... = {} else {}` pattern and there is a rustfix to obtain `let ... = ({}) else {}`. I'm not sure whether the `.fixed` files are checked by the tooling that they compile. But if there is no such check, it would be neat to make sure that `let ... = ({}) else {}` compiles.~~ → *test added by e7730dc* * ~~rust-lang#92069 got closed as fixed, but no regression test was added. Not sure it's worth to add one.~~ → *test added by 5bd7106* * ~~consistency between `let else` and `if let` regarding lifetimes and drop order: rust-lang#93628 (comment) → *test added by 2d8460e* Edit: they are all tested now. ### Possible future work / Refutable destructuring assignments [RFC 2909](https://rust-lang.github.io/rfcs/2909-destructuring-assignment.html) specifies destructuring assignment, allowing statements like `FooBar { a, b, c } = foo();`. As it was stabilized, destructuring assignment only allows *irrefutable* patterns, which before the advent of `let else` were the only patterns that `let` supported. So the combination of `let else` and destructuring assignments gives reason to think about extensions of the destructuring assignments feature that allow refutable patterns, discussed in rust-lang#93995. A naive mapping of `let else` to destructuring assignments in the form of `Some(v) = foo() else { ... };` might not be the ideal way. `let else` needs a diverging `else` clause as it introduces new bindings, while assignments have a default behaviour to fall back to if the pattern does not match, in the form of not performing the assignment. Thus, there is no good case to require divergence, or even an `else` clause at all, beyond the need for having *some* introducer syntax so that it is clear to readers that the assignment is not a given (enums and structs look similar). There are better candidates for introducer syntax however than an empty `else {}` clause, like `maybe` which could be added as a keyword on an edition boundary: ```Rust let mut v = 0; maybe Some(v) = foo(&v); maybe Some(v) = foo(&v) else { bar() }; ``` Further design discussion is left to an RFC, or the linked issue.
I'm not sure if this issue was closed for good but I have a question if expansion of the syntax is considered to be implemented. I was actively using let Ok((ws_stream, _)) = tokio_tungstenite::connect_async(&url).await else {
tracing::warn!("[wss] Cannot connect to RPC endpoint. Waiting and reconnecting");
tokio::time::sleep(reconnect_delay).await;
continue;
}; It's cool and beans but at some point you realize that you want to know why it is failing, so in order to do so you need to write: let ws_stream = match tokio_tungstenite::connect_async(&url).await {
Ok((ws_stream, _)) => ws_stream,
Err(e) => {
tracing::warn!("[wss] Cannot connect to RPC endpoint: {}. Waiting and reconnecting", e);
tokio::time::sleep(reconnect_delay).await;
continue;
}
}; Which is quite verbose for such a simple task. Add some more logic and suddenly out of the function for 100 lines you have 10-20 eaten just by the following pattern, with a lot of foos like in Approach with let Ok((ws_stream, _)) = tokio_tungstenite::connect_async(&url).await
.map_err(|e| tracing::warn!("[wss] Cannot connect to RPC endpoint: {}. Waiting and reconnecting", e)) else {
tokio::time::sleep(reconnect_delay).await;
continue;
}; It also splits error handling in multiple blocks which is harder to read (and write). So for now the only way to use Honestly I would like to have an optional binding so both programs I wrote below would be accepted: let Ok((ws_stream, _)) = tokio_tungstenite::connect_async(&url).await else {
tracing::warn!("[wss] Cannot connect to RPC endpoint. Waiting and reconnecting");
tokio::time::sleep(reconnect_delay).await;
continue;
}; and let Ok((ws_stream, _)) = tokio_tungstenite::connect_async(&url).await else e {
tracing::warn!("[wss] Cannot connect to RPC endpoint: {:?}. Waiting and reconnecting", e);
tokio::time::sleep(reconnect_delay).await;
continue;
}; Since it is currently an invalid program this feature can be added in backward-compatible manner. I know this was covered in https://github.com/Fishrock123/rfcs/blob/let-else/text/0000-let-else.md#let-else-match but I suggest to reiterate and see if it can be added as an optional feature. |
One other thing to consider as a limitation of let-else is the fact that the new destructuring assignment doesn't work with it, meaning that if you wanted to do the same as the above without the |
@Pzixel The answer to this is suggested to be to use The problem with e.g. There could be an argument that this is still useful, however it mostly seems that this happens with I do agree with this sentiment regarding
|
Tracking issue: rust-lang/rust#87335
Introduce a new
let PATTERN: TYPE = EXPRESSION else DIVERGING_BLOCK;
construct (informally called alet-else statement), the counterpart of if-let expressions.
If the pattern match from the assigned expression succeeds, its bindings are introduced into the
surrounding scope. If it does not succeed, it must diverge (return
!
, e.g. return or break).Technically speaking, let-else statements are refutable
let
statements.The expression has some restrictions, notably it may not be an
ExpressionWithBlock
orLazyBooleanExpression
.This RFC is a modernization of a 2015 RFC (pull request 1303) for an almost identical feature.
Rendered
Thanks to all the folks on Zulip who were willing to discuss this proposal once again and helped me draft this RFC.
In particular: Josh Triplett, scottmcm, Mario Carneiro, Frank Steffahn, Dirkjan Ochtman, and bstrie.
Zulip topic: https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/.60let.20pattern.20.3D.20expr.20else.20.7B.20.2E.2E.2E.20.7D.60.20statements