Skip to content

Commit 3438586

Browse files
committed
let-else draft updates from zulip
Specifically bstrie's suggestion of `ExpressionWithoutBlock`. Also some spelling fixes and example updates.
1 parent 2b92ef5 commit 3438586

File tree

1 file changed

+79
-43
lines changed

1 file changed

+79
-43
lines changed

text/0000-let-else.md

+79-43
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# Summary
77
[summary]: #summary
88

9-
Introduce a new `let PATTERN = EXPRESSION else { /* DIVERGING BLOCK */ };` construct (informally called a
9+
Introduce a new `let PATTERN = EXPRESSION_WITHOUT_BLOCK else DIVERGING_BLOCK;` construct (informally called a
1010
**let-else statement**), the counterpart of if-let expressions.
1111

1212
If the pattern match from the assigned expression succeeds, its bindings are introduced *into the
@@ -22,8 +22,8 @@ This RFC is a modernization of a [2015 RFC (pull request 1303)][old-rfc] for an
2222
It is the natural counterpart to `if let`, just as `else` is to regular `if`.
2323

2424
[if-let expressions][if-let] offer a succinct syntax for pattern matching single patterns.
25-
This is particularly useful for unwrapping types like `Option`, particularly those with a clear "success" varient
26-
for the given context but no specific "failure" varient.
25+
This is particularly useful for unwrapping types like `Option`, particularly those with a clear "success" variant
26+
for the given context but no specific "failure" variant.
2727
However, an if-let expression can only create bindings within its body, which can force
2828
rightward drift, introduce excessive nesting, and separate conditionals from error paths.
2929

@@ -35,7 +35,7 @@ which require intermediary bindings (usually of the same name).
3535

3636
## Examples
3737

38-
The following three code examples are possible options with current Rust code.
38+
The following two code examples are possible options with current Rust code.
3939

4040
```rust
4141
if let Some(a) = x {
@@ -73,22 +73,7 @@ do_something_with(a, b, c);
7373
// ...
7474
```
7575

76-
```rust
77-
let a = if let Some(a) { a } else {
78-
return Err("bad x"),
79-
};
80-
let b = if let Some(b) { b } else {
81-
return Err("bad y"),
82-
};
83-
let c = if let Some(c) { c } else {
84-
return Err("bad z"),
85-
};
86-
// ...
87-
do_something_with(a, b, c);
88-
// ...
89-
```
90-
91-
All three of the above examples would be able to be written as:
76+
Both of the above examples would be able to be written as:
9277

9378
```rust
9479
let Some(a) = x else {
@@ -107,6 +92,49 @@ do_something_with(a, b, c);
10792

10893
which succinctly avoids bindings of the same name, rightward shift, etc.
10994

95+
let-else is even more useful when dealing with enums which are not `Option`/`Result`, consider how the
96+
following code would look without let-else (transposed from a real-world project written in part by the author):
97+
98+
```rust
99+
impl ActionView {
100+
pub(crate) fn new(history: &History<Action>) -> Result<Self, eyre::Report> {
101+
let mut iter = history.iter();
102+
let event = iter
103+
.next()
104+
.ok_or_else(|| eyre::eyre!("Entity has no history"))?;
105+
106+
let Action::Register {
107+
actor: String,
108+
x: Vec<String>
109+
y: u32,
110+
z: String,
111+
} = event.action().clone() else {
112+
// RFC Author's note:
113+
// Without if-else this was separated from the conditional
114+
// by a substantial block of code which now follows below.
115+
Err(eyre::eyre!("must begin with a Register action"))
116+
};
117+
118+
let created = *event.created();
119+
let mut view = ActionView {
120+
registered_by: (actor, created),
121+
a: (actor.clone(), x, created),
122+
b: (actor.clone(), y, created),
123+
c: (z, created),
124+
d: Vec::new(),
125+
126+
e: None,
127+
f: None,
128+
g: None,
129+
};
130+
for event in iter {
131+
view.update(&event)?;
132+
}
133+
Ok(view)
134+
}
135+
}
136+
```
137+
110138
## Versus `match`
111139

112140
It is possible to use `match` statements to emulate this today, but at a
@@ -131,7 +159,7 @@ let Some(ref subpage_layer_info) = layer_properties.subpage_layer_info else {
131159
# Guide-level explanation
132160
[guide-level-explanation]: #guide-level-explanation
133161

134-
A common pattern in non-trivial code where static guarentees can not be fully met (e.g. I/O, network or otherwise) is to check error cases when possible before proceding,
162+
A common pattern in non-trivial code where static guarantees can not be fully met (e.g. I/O, network or otherwise) is to check error cases when possible before proceeding,
135163
and "return early", by constructing an error `Result` or an empty `Option`, and returning it before the "happy path" code.
136164

137165
This pattern serves no practical purpose to a computer, but it is helpful for humans interacting with the code.
@@ -155,17 +183,19 @@ let Some(a) = an_option else {
155183
This is a counterpart to `if let` expressions, and the pattern matching works identically, except that the value from the pattern match
156184
is assigned to the surrounding scope rather than the block's scope.
157185

158-
# Reference-level explanation
186+
# Reference-level explanations
159187
[reference-level-explanation]: #reference-level-explanation
160188

161189
let-else is syntactical sugar for either `if let { assignment } else {}` or `match`, where the non-matched case diverges.
162190

163191
Any expression may be put into the expression position except an `if {} else {}` as explain below in [drawbacks][].
164192
While `if {} else {}` is technically feasible this RFC proposes it be disallowed for programmer clarity to avoid an `... else {} else {}` situation.
193+
Rust already provides us with such a restriction, [`ExpressionWithoutBlock`][expressions].
165194

166195
Any pattern that could be put into if-let's pattern position can be put into let-else's pattern position.
167196

168197
The `else` block must diverge. This could be a keyword which diverges (returns `!`), or a panic.
198+
This likely necessitates a new subtype of `BlockExpression`, something like `BlockExpressionDiverging`.
169199
Allowed keywords:
170200
- `return`
171201
- `break`
@@ -182,34 +212,39 @@ accessible as they would normally be.
182212
"Must diverge" is an unusual requirement, which doesn't exist elsewhere in the language as of the time of writing,
183213
and might be difficult to explain or lead to confusing errors for programmers new to this feature.
184214

215+
This also neccesitates a new block expression subtype, something like `BlockExpressionDiverging`.
216+
185217
## `let PATTERN = if {} else {} else {};`
186218

187219
One unfortunate combination of this feature with regular if-else expressions is the possibility of `let PATTERN = if { a } else { b } else { c };`.
188220
This is likely to be unclear if anyone writes it, but does not pose a syntactical issue, as `let PATTERN = if y { a } else { b };` should always be
189-
interperited as `let Enum(x) = (if y { a } else { b });` (still a compile error as there no diverging block: `error[E0005]: refutable pattern in local binding: ...`)
221+
interpreted as `let Enum(x) = (if y { a } else { b });` (still a compile error as there no diverging block: `error[E0005]: refutable pattern in local binding: ...`)
190222
because the compiler won't interpret it as `let PATTERN = (if y { a }) else { b };` since `()` is not an enum.
191223

192-
This can be overcome by making a raw if-else in the expression position a compile error and instead requring that parentheses are inserted to disambiguate:
224+
This can be overcome by making a raw if-else in the expression position a compile error and instead requiring that parentheses are inserted to disambiguate:
193225
`let PATTERN = (if { a } else { b }) else { c };`.
194226

227+
Rust already provides us with such a restriction, and so the expression can be restricted to be a [`ExpressionWithoutBlock`][expressions].
228+
195229
# Rationale and alternatives
196230
[rationale-and-alternatives]: #rationale-and-alternatives
197231

198232
let-else attempts to be as consistent as possible to similar existing syntax.
199233

200-
Fundimentally it is treated as a `let` statement, necessitating an assignment and the trailing semicolon.
234+
Fundamentally it is treated as a `let` statement, necessitating an assignment and the trailing semicolon.
201235

202236
Pattern matching works identically to if-let, no new "negation" pattern matching rules are introduced.
203237

204-
The `else` must be followed by a block, as in `if {} else {}`.
238+
The expression can be any [`ExpressionWithoutBlock`][expressions], in order to prevent `else {} else {}` confusion, as noted in [drawbacks][#drawbacks].
239+
240+
The `else` must be followed by a block, as in `if {} else {}`. This else block must be diverging as the outer
241+
context cannot be guaranteed to continue soundly without assignment, and no alternate assignment syntax is provided.
205242

206-
The else block must be diverging as the outer context cannot be guarenteed to continue soundly without assignment, and no alternate assignment syntax is provided.
243+
## Alternatives
207244

208245
While this feature can effectively be covered by functions such `or_or`/`ok_or_else` on the `Option` and `Result` types combined with the Try operator (`?`),
209246
such functions do not exist automatically on custom enum types and require non-obvious and non-trivial implementation, and may not be map-able
210-
to `Option`/`Result`-style functions at all (especially for enums where the "success" varient is contextual and there are many varients).
211-
212-
## Alternatives
247+
to `Option`/`Result`-style functions at all (especially for enums where the "success" variant is contextual and there are many variants).
213248

214249
### `let PATTERN = EXPR else return EXPR;`
215250

@@ -230,19 +265,19 @@ This was originally suggested in the old RFC, comment at https://github.com/rust
230265
A fall-back assignment alternate to the diverging block has been proposed multiple times in relation to this feature in the [original rfc][] and also in out-of-RFC discussions.
231266

232267
This RFC avoids this proposal, because there is no clear syntax to use for it which would be consistent with other existing features.
233-
Also use-cases for having a single fall-back are much more rare and ususual, where as use cases for the diverging block are very common.
268+
Also use-cases for having a single fall-back are much more rare and unusual, where as use cases for the diverging block are very common.
234269
This RFC proposes that most fallback cases are sufficiently or better covered by using `match`.
235270

236271
An example, using a proposal to have the binding be visible and assignable from the `else`-block.
237272
Note that this is incompatible with this RFC and could probably not be added as an extension from this RFC.
238273

239274
```rust
240275
enum AnEnum {
241-
Varient1(u32),
242-
Varient2(String),
276+
Variant1(u32),
277+
Variant2(String),
243278
}
244279

245-
let AnEnum::Varient1(a) = x else {
280+
let AnEnum::Variant1(a) = x else {
246281
a = 42;
247282
};
248283
```
@@ -251,11 +286,11 @@ Another potential alternative for fall-back which could be added with an additio
251286

252287
```rust
253288
enum AnEnum {
254-
Varient1(u32),
255-
Varient2(String),
289+
Variant1(u32),
290+
Variant2(String),
256291
}
257292

258-
let AnEnum::Varient1(a) = x else assign a {
293+
let AnEnum::Variant1(a) = x else assign a {
259294
a = 42;
260295
};
261296
```
@@ -265,7 +300,7 @@ let AnEnum::Varient1(a) = x else assign a {
265300
The [old RFC][old-rfc] originally proposed this general feature via some kind of pattern negation as `if !let PAT = EXPR { BODY }`.
266301

267302
This RFC avoids adding any kind of new or special pattern matching rules. The pattern matching works as it does for if-let.
268-
The general consensus in the old RFC was also that the negation syntax is much less clear than `if PATTERN = EXPR else { /* diverge */ };`,
303+
The general consensus in the old RFC was also that the negation syntax is much less clear than `if PATTERN = EXPR_WITHOUT_BLOCK else { /* diverge */ };`,
269304
and partway through that RFC's lifecycle it was updated to be similar to this RFC's proposed let-else syntax.
270305

271306
### Complete Alternative
@@ -286,9 +321,9 @@ proposal except for the choice of keywords.
286321
The `match` alternative in particular is fairly prevalent in rust code on projects which have many possible error conditions.
287322

288323
The Try operator allows for an `ok_or` alternative to be used where the types are only `Option` and `Result`,
289-
which is considered to be idomatic rust.
324+
which is considered to be idiomatic rust.
290325

291-
// TODO link to examples, provide internal stistics, gather statistics from the rust compiler itself, etc.
326+
// TODO link to examples, provide internal statistics, gather statistics from the rust compiler itself, etc.
292327

293328
# Unresolved questions
294329
[unresolved-questions]: #unresolved-questions
@@ -307,11 +342,11 @@ If fall-back assignment as discussed above in [rationale-and-alternatives][] is
307342

308343
```rust
309344
enum AnEnum {
310-
Varient1(u32),
311-
Varient2(String),
345+
Variant1(u32),
346+
Variant2(String),
312347
}
313348

314-
let AnEnum::Varient1(a) = x else assign a {
349+
let AnEnum::Variant1(a) = x else assign a {
315350
a = 42;
316351
};
317352
```
@@ -324,6 +359,7 @@ let Ok(a) = x else match {
324359
}
325360
```
326361

362+
[expressions]: https://doc.rust-lang.org/reference/expressions.html#expressions
327363
[old-rfc]: https://github.com/rust-lang/rfcs/pull/1303
328364
[if-let]: https://github.com/rust-lang/rfcs/blob/master/text/0160-if-let.md
329365
[swift]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID525

0 commit comments

Comments
 (0)