Skip to content

Commit c0c2fcc

Browse files
committed
let-else, FCP updates
Hopefully this will be my last round of updates. - Added some unresolved questions. - Fixed some typos / english language clarification. - Clarified that all expressions ending in `}` should be disallowed. - Fixed some errors in examples. - Added section on `, else`. - Updated the `unless let` section to be "Introducer syntax" with the noted keyword being `guard`. - Noted the `DIVERGING_EXPR` section with more detail.
1 parent 54bca55 commit c0c2fcc

File tree

1 file changed

+52
-21
lines changed

1 file changed

+52
-21
lines changed

text/0000-let-else.md

+52-21
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Introduce a new `let PATTERN: TYPE = EXPRESSION else DIVERGING_BLOCK;` construct
1212
If the pattern match from the assigned expression succeeds, its bindings are introduced *into the
1313
surrounding scope*. If it does not succeed, it must diverge (return `!`, e.g. return or break).
1414
Technically speaking, let-else statements are refutable `let` statements.
15-
The expression has some restrictions, notably it may not be an `ExpressionWithBlock` or `LazyBooleanExpression`.
15+
The expression has some restrictions, notably it may not end with an `}` or be just a `LazyBooleanExpression`.
1616

1717
This RFC is a modernization of a [2015 RFC (pull request 1303)][old-rfc] for an almost identical feature.
1818

@@ -151,7 +151,7 @@ let features = match geojson {
151151
};
152152
```
153153

154-
However, with if-let this could be more succinct & clear:
154+
However, with let-else this could be more succinct & clear:
155155

156156
```rust
157157
let GeoJson::FeatureCollection(features) = geojson else {
@@ -209,7 +209,9 @@ let (each, binding) = match expr {
209209
```
210210

211211
Most expressions may be put into the expression position with two restrictions:
212-
1. May not include a block outside of parenthesis. (Must be an [`ExpressionWithoutBlock`][expressions].)
212+
1. May not include a block outside of parenthesis.
213+
- Must be an [`ExpressionWithoutBlock`][expressions].
214+
- [`GroupedExpression`][grouped-expr]-s ending with a `}` are additionally not allowed and must be put in parenthesis.
213215
2. May not be just a lazy boolean expression (`&&` or `||`). (Must not be a [`LazyBooleanExpression`][lazy-boolean-operators].)
214216

215217
While allowing e.g. `if {} else {}` directly in the expression position is technically feasible this RFC proposes it be
@@ -231,7 +233,7 @@ accessible as they would normally be.
231233
For patterns which match multiple variants, such as through the `|` (or) syntax, all variants must produce the same bindings (ignoring additional bindings in uneven patterns),
232234
and those bindings must all be names the same. Valid example:
233235
```rust
234-
let Some(x) | MyEnum::VariantA(_, _, x) | MyEnum::VariantB { x, .. } = a else { return; };
236+
let MyEnum::VariantA(_, _, x) | MyEnum::VariantB { x, .. } = a else { return; };
235237
```
236238

237239
let-else does not combine with the `let` from if-let, as if-let is not actually a _let statement_.
@@ -247,7 +249,7 @@ Desugars to
247249

248250
```rust
249251
let x = match y {
250-
Some(x) => y,
252+
Some(x) => x,
251253
_ => {
252254
let nope: ! = { return; };
253255
match nope {}
@@ -263,7 +265,7 @@ let x = match y {
263265
"Must diverge" is an unusual requirement, which doesn't exist elsewhere in the language as of the time of writing,
264266
and might be difficult to explain or lead to confusing errors for programmers new to this feature.
265267

266-
However, rustc does have support for representing the divergence through the type-checker via `!` or any other uninhabitable type,
268+
However, rustc does have support for representing the divergence through the type-checker via `!` or any other uninhabited type,
267269
so the implementation is not a problem.
268270

269271
## `let PATTERN = if {} else {} else {};`
@@ -322,9 +324,17 @@ This is supposed to help disambiguate let-else statements from other code with b
322324
This RFC avoids this as it would mean losing symmetry with if-else and if-let-else, and would require adding a new keyword.
323325
Adding a new keyword could mean more to teach and could promote even more special casing around let-else's semantics.
324326

325-
### `unless let ... {}` / `try let ... {}`
327+
### Comma-before-else (`, else { ... }`)
326328

327-
Another often proposed alternative is to add an extra keyword to the beginning of the let-else statement, to denote that it is different than a regular `let` statement.
329+
Another proposal very similar to renaming `else` it to have it be proceeded by some character such as a comma.
330+
331+
It is possible that adding such additional separating syntax would make combinations with expressions which have blocks
332+
easier to read and less ambiguous, but is also generally inconsistent with the rest of the rust language at time of writing.
333+
334+
### Introducer syntax (`guard let ... {}`)
335+
336+
Another often proposed alternative is to add some introducer syntax (usually an extra keyword) to the beginning of the let-else statement,
337+
to denote that it is different than a regular `let` statement.
328338

329339
One possible benefit of adding a keyword is that it could make a possible future extension for similarity to the (yet unimplemented) [if-let-chains][] feature more straightforward.
330340
However, as mentioned in the [future-possibilities][] section, this is likely not necessary.
@@ -348,17 +358,18 @@ and partway through that RFC's lifecycle it was updated to be similar to this RF
348358

349359
The `if !let` alternative syntax would also share the binding drawback of the `unless let` alternative syntax.
350360

351-
### `let PATTERN = EXPR else return EXPR;`
361+
### `let PATTERN = EXPR else DIVERGING_EXPR;`
352362

353-
A potential alternative to requiring parentheses in `let PATTERN = (if { a } else { b }) else { c };` is to change the syntax of the `else` to no longer be a block
354-
but instead an expression which starts with a diverging keyword, such as `return` or `break`.
363+
A potential alternative to requiring parentheses in `let PATTERN = (if { a } else { b }) else { c };`
364+
is to change the syntax of the `else` to no longer be a block but instead _any_ expression which diverges,
365+
such as a `return`, `break`, or any block which diverges.
355366

356367
Example:
357-
```
368+
```rust
358369
let Some(foo) = some_option else return None;
359370
```
360371

361-
This RFC avoids this because it is overall less consistent with `else` from if-else, which require blocks.
372+
This RFC avoids this because it is overall less consistent with `else` from if-else, which requires block expressions.
362373

363374
This was originally suggested in the old RFC, comment at https://github.com/rust-lang/rfcs/pull/1303#issuecomment-188526691
364375

@@ -409,7 +420,7 @@ match thing {
409420
}
410421
```
411422

412-
However this is not an obvious opposite io if-let, and would introduce an entirely new positional meaning of `let`.
423+
However this is not an obvious opposite to if-let, and would introduce an entirely new positional meaning of `let`.
413424

414425
### `||` in pattern-matching
415426

@@ -430,13 +441,13 @@ let Some(x) = a || b || { return; };
430441
Combined with `&&` as proposed in if-let-chains, constructs such as the following are conceivable:
431442

432443
```rust
433-
let Enum::Var1(x) = a || b || { return anyhow!("Bad x"); } && let Some(z) = x || y;
444+
let Enum::Var1(x) = a || b || { return anyhow!("Bad x"); } && let Some(z) = x || y || { break; };
434445
// Complex. Both x and z are now in scope.
435446
```
436447

437448
This is not a simple construct, and could be quite confusing to newcomers
438449

439-
That being said, such a thing is not perfectly obvious to write today, and might be just as confusing to read:
450+
That said, such a thing is not perfectly obvious to write today, and might be just as confusing to read:
440451
```rust
441452
let x = if let Enum::Var1(v) = a {
442453
v
@@ -445,9 +456,12 @@ let x = if let Enum::Var1(v) = a {
445456
} else {
446457
anyhow!("Bad x")
447458
};
448-
let z = match x {
449-
Some(z) => z,
450-
_ => y,
459+
let z = if let Some(v) = x {
460+
v
461+
} else if let Some(v) = y {
462+
v
463+
} else {
464+
break;
451465
};
452466
// Complex. Both x and z are now in scope.
453467
```
@@ -488,8 +502,25 @@ which is considered to be idiomatic rust.
488502
# Unresolved questions
489503
[unresolved-questions]: #unresolved-questions
490504

491-
None known at time of writing due to extensive pre-discussion in Zulip:
492-
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
505+
## Readability in practice
506+
507+
Will `let ... else { ... };` be clear enough to humans in practical code, or will some introducer syntax be desirable?
508+
509+
## Conflicts with if-let-chains
510+
511+
Does this conflict too much with the if-let-chains RFC or vice-versa?
512+
513+
Neither this feature nor that feature should be stabilized without considering the other.
514+
515+
## Amount of special cases
516+
517+
Are there too many special-case interactions with other features?
518+
519+
## Grammar clarity
520+
521+
Does the grammar need to be clarified?
522+
523+
This RFC has some slightly unusual grammar requirements.
493524

494525
# Future possibilities
495526
[future-possibilities]: #future-possibilities

0 commit comments

Comments
 (0)