Skip to content

Commit 89c5b6e

Browse files
committed
let-else, PFCP updates
- Clarify that `: TYPE` is allowed, as per Josh - Note the expression restriction more accurately and with more detail, as per Niko - Removed the option-chaining example, because many people keep stopping at that to comment. - Note a macro as an (unfavorable) alternative.
1 parent cab12fc commit 89c5b6e

File tree

1 file changed

+65
-76
lines changed

1 file changed

+65
-76
lines changed

text/0000-let-else.md

+65-76
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
# Summary
77
[summary]: #summary
88

9-
Introduce a new `let PATTERN = EXPRESSION_WITHOUT_BLOCK else DIVERGING_BLOCK;` construct (informally called a
9+
Introduce a new `let PATTERN: TYPE = EXPRESSION 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
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`.
1516

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

@@ -35,87 +36,60 @@ which require intermediary bindings (usually of the same name).
3536

3637
## Examples
3738

38-
The following two code examples are possible options with current Rust code.
39+
let-else is particularly useful when dealing with enums which are not `Option`/`Result`, and as such do not have access to e.g. `ok_or()`.
40+
Consider the following example transposed from a real-world project written in part by the author:
3941

42+
Without let-else, as this code was originally written:
4043
```rust
41-
if let Some(x) = xyz {
42-
if let Some(y) = x.thing() {
43-
if let Some(z) = y.thing() {
44-
// ...
45-
do_something_with(z);
46-
// ...
44+
impl ActionView {
45+
pub(crate) fn new(history: &History<Action>) -> Result<Self, eyre::Report> {
46+
let mut iter = history.iter();
47+
let event = iter
48+
.next()
49+
// RFC comment: ok_or_else works fine to early return when working with `Option`.
50+
.ok_or_else(|| eyre::eyre!("Entity has no history"))?;
51+
52+
if let Action::Register {
53+
actor: String,
54+
x: Vec<String>
55+
y: u32,
56+
z: String,
57+
} = event.action().clone() {
58+
let created = *event.created();
59+
let mut view = ActionView {
60+
registered_by: (actor, created),
61+
a: (actor.clone(), x, created),
62+
b: (actor.clone(), y, created),
63+
c: (z, created),
64+
d: Vec::new(),
65+
66+
e: None,
67+
f: None,
68+
g: None,
69+
};
70+
for event in iter {
71+
view.update(&event)?;
72+
}
73+
74+
// more lines omitted
75+
76+
Ok(view)
4777
} else {
48-
info!("z was bad");
49-
return Err("bad z");
78+
// RFC comment: Far away from the associated conditional.
79+
Err(eyre::eyre!("must begin with a Register action"));
5080
}
51-
} else {
52-
info!("y was bad");
53-
return Err("bad y");
5481
}
55-
} else {
56-
info!("x was bad");
57-
return Err("bad x");
5882
}
5983
```
6084

61-
```rust
62-
let x = match xyz {
63-
Some(x) => x,
64-
_ => {
65-
info!("x was bad");
66-
return Err("bad x")
67-
},
68-
};
69-
let y = match x.thing() {
70-
Some(y) => y,
71-
_ => {
72-
info!("y was bad");
73-
return Err("bad y")
74-
},
75-
};
76-
let z = match y.thing() {
77-
Some(z) => z,
78-
_ => return {
79-
info!("z was bad");
80-
Err("bad z")
81-
},
82-
};
83-
// ...
84-
do_something_with(z);
85-
// ...
86-
```
87-
88-
Both of the above examples would be able to be written as:
89-
90-
```rust
91-
let Some(x) = xyz else {
92-
info!("x was bad");
93-
return Err("bad x");
94-
};
95-
let Some(y) = x.thing() else {
96-
info!("y was bad");
97-
return Err("bad y");
98-
};
99-
let Some(z) = y.thing() else {
100-
info!("z was bad");
101-
return Err("bad z");
102-
};
103-
// ...
104-
do_something_with(z);
105-
// ...
106-
```
107-
108-
which succinctly avoids bindings of the same name, rightward shift, etc.
109-
110-
let-else is even more useful when dealing with enums which are not `Option`/`Result`, consider how the
111-
following code would look without let-else (transposed from a real-world project written in part by the author):
112-
85+
With let-else:
11386
```rust
11487
impl ActionView {
11588
pub(crate) fn new(history: &History<Action>) -> Result<Self, eyre::Report> {
11689
let mut iter = history.iter();
11790
let event = iter
11891
.next()
92+
// RFC comment: ok_or_else works fine to early return when working with `Option`.
11993
.ok_or_else(|| eyre::eyre!("Entity has no history"))?;
12094

12195
let Action::Register {
@@ -124,9 +98,7 @@ impl ActionView {
12498
y: u32,
12599
z: String,
126100
} = event.action().clone() else {
127-
// RFC Author's note:
128-
// Without if-else this was separated from the conditional
129-
// by a substantial block of code which now follows below.
101+
// RFC comment: Directly located next to the associated conditional.
130102
return Err(eyre::eyre!("must begin with a Register action"));
131103
};
132104

@@ -145,6 +117,9 @@ impl ActionView {
145117
for event in iter {
146118
view.update(&event)?;
147119
}
120+
121+
// more lines omitted
122+
148123
Ok(view)
149124
}
150125
}
@@ -233,12 +208,17 @@ let (each, binding) = match expr {
233208
};
234209
```
235210

236-
Any expression may be put into the expression position except an `if {} else {}` as explain below in [drawbacks][].
237-
While `if {} else {}` is technically feasible this RFC proposes it be disallowed for programmer clarity to avoid an `... else {} else {}` situation.
238-
Rust already provides us with such a restriction, [`ExpressionWithoutBlock`][expressions].
211+
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].)
213+
2. May not be just a lazy boolean expression (`&&` or `||`). (Must not be a [`LazyBooleanExpression`][lazy-boolean-operators].)
239214

240-
Any pattern that could be put into if-let's pattern position can be put into let-else's pattern position, except a match to a boolean when
241-
a lazy boolean operator is present (`&&` or `||`), for reasons noted in [future-possibilities][].
215+
While allowing e.g. `if {} else {}` directly in the expression position is technically feasible this RFC proposes it be
216+
disallowed for programmer clarity so as to avoid `... else {} else {}` situations as discussed in the [drawbacks][] section.
217+
Boolean matches are not useful with let-else and so lazy boolean expressions are disallowed for reasons noted in [future-possibilities][].
218+
These types of expressions can still be used when combined in a less ambiguous manner with parenthesis, thus forming a [`GroupedExpression`][grouped-expr],
219+
which is allowed under the two expression restrictions.
220+
221+
Any refutable pattern that could be put into if-let's pattern position can be put into let-else's pattern position.
242222

243223
If the pattern is irrefutable, rustc will emit the `irrefutable_let_patterns` warning lint, as it does with an irrefutable pattern in an `if let`.
244224

@@ -474,6 +454,13 @@ let z = match x {
474454

475455
This is, as stated, a much more complex alternative interacting with much more of the language, and is also not an obvious opposite of if-let expressions.
476456

457+
### Macro
458+
459+
Another suggested solution is to create a macro which handles this.
460+
A crate containing such a macro is mentioned in the [Prior art](prior-art) section of this RFC.
461+
462+
This crate has not been widely used in the rust crate ecosystem with only 47k downloads over the ~6 years it has existed at the time of writing.
463+
477464
### Null Alternative
478465

479466
Don't make any changes; use existing syntax like `match` (or `if let`) as shown in the motivating example, or write macros to simplify the code.
@@ -623,10 +610,12 @@ because it is confusing to read syntactically, and it is functionally similar to
623610
[`const`]: https://doc.rust-lang.org/reference/items/constant-items.html
624611
[`static`]: https://doc.rust-lang.org/reference/items/static-items.html
625612
[expressions]: https://doc.rust-lang.org/reference/expressions.html#expressions
613+
[grouped-expr]: https://doc.rust-lang.org/reference/expressions/grouped-expr.html
626614
[guard-crate]: https://crates.io/crates/guard
627615
[guard-repo]: https://github.com/durka/guard
628616
[if-let]: https://rust-lang.github.io/rfcs/0160-if-let.html
629617
[if-let-chains]: https://rust-lang.github.io/rfcs/2497-if-let-chains.html
618+
[lazy-boolean-operators]: https://doc.rust-lang.org/reference/expressions/operator-expr.html#lazy-boolean-operators
630619
[never-type]: https://doc.rust-lang.org/std/primitive.never.html
631620
[old-rfc]: https://github.com/rust-lang/rfcs/pull/1303
632621
[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)