-
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: if- and while-let-chains #2260
Conversation
RFC #2046 does help you with rightward drift: 'label :{
let expr = if let ExprAddrOf(_, ref expr) = expr.node {
expr
} else {
break 'label None;
};
let expr = if let ExprArray(ref exprs) = expr.node {
exprs
} else {
break 'label None;
};
let expr = if let Some(expr) = exprs.last() {
expr
} else {
break 'label None;
};
// ...
Some((lit.as_str(), exprs.len()))
} Now if you add one simple macro, 'label : {
let expr = unwrap_or_none!(ExprAddrOf(_, ref expr) = expr.node, 'label);
let exprs = unwrap_or_none!(ExprArray(ref exprs) = expr.node, 'label);
let expr = unwrap_or_none!(Some(expr) = exprs.last(), 'label);
let lit = unwrap_or_none!(ExprLit(ref lit) = expr.node, 'label);
let lit = unwrap_or_none!(LitKind::Str(ref lit, _) = lit.node, 'label);
Some((lit.as_str(), exprs.len()))
} This allows you to do non-trivial code between the let declarations, something that is very inconvenient with this proposal, so it fixes the ergonomics issues much better than this RFC. |
With this, we don't need
The |
Another alternative to this RFC: if guards on if's: fn get_argument_fmtstr_parts(expr: &Expr) -> Option<(InternedString, usize)> {
if let ExprAddrOf(_, ref expr) = expr.node // &["…", "…", …]
if let ExprArray(ref exprs) = expr.node
if let Some(expr) = exprs.last()
if let ExprLit(ref lit) = expr.node
if let LitKind::Str(ref lit, _) = lit.node {
Some((lit.as_str(), exprs.len()))
} else {
None
}
} This would be more consistent to what match is doing, and I think there are no parser ambiguities. |
I find this syntax very confusing. At a glance this looks very much like parallel I still do think the best way to deal with this kind of right-ward drift is with early returns, with something like |
I agree with @Wyverald that the , syntax is confusing. I wondered at first too whether it was "or" mode or "and" mode (it's "and" mode). I think my suggestion for |
@Havvy You certainely could write @Wyverald We did not consider that this could be confused with parallel assignment at all. If that is an inference that many make, we need to change the syntax of course. @est31 We didn't consider this, and that is true. But the first non-macro snippet you listed is not very ergonomic or readable and has a lot of repetition. The macro does reduce this, but repeats I did briefly consider: (and yes, there should be no parser ambiguities) fn get_argument_fmtstr_parts(expr: &Expr) -> Option<(InternedString, usize)> {
if let ExprAddrOf(_, ref expr) = expr.node // &["…", "…", …]
if let ExprArray(ref exprs) = expr.node
if let Some(expr) = exprs.last()
if let ExprLit(ref lit) = expr.node
if let LitKind::Str(ref lit, _) = lit.node {
Some((lit.as_str(), exprs.len()))
} else {
None
}
} but went on since it is slightly more verbose. However, it aligns pretty well and is a syntax I could live with. I will certainly add it as an alternative if it is not changed to be the main proposal instead. |
@est31 If we go with your proposed syntax however, we need to consider what that means for |
Yeah, that's why I added a macro snippet. The first snippet was only to reduce rightward drift.
You can probably minimize this further by creating a macro inside the
I don't have any views/thoughts on the precedence. |
Right. I will update the RFC with a more nuanced description in a while (holidays, so, yeah... ^,^)
Sounds about right. However, the macro is specific to returning
We'll have to resolve this together tho I think unless we're OK with shutting the door to |
This is a very well written RFC. Thanks for writing it. Well written RFCs really improve this process and are are so very helpful for all involved. For reference, this topic was previously discussed in #929. |
Short review (I'll probably elaborate later):
Syntax is the most important aspect in this particular case - the whole point of the change is resyntax nested conditions and conditions with weird inverted order into something nicer looking. |
@petrochenkov ohhh, this
|
I am sure I have written that way on many occasions.
I think the reasons we state in the RFC for rejecting this are valid.
I am not a fan of this syntax. It is inconsistent with the order already used in I also think that patterns are more often than not shorter than what they pattern match on, so reversed order would be particularly bad for alignment. The keyword I'm assuming that
My bad. I could have been clearer - what I meant really is: the most important bit is reducing rightward drift and improving ergonomics - the exact syntax can be changed ;) Updated above. |
@matklad That's a very nice and interesting solution indeed =) With slight change: let name = meta.name();
when {
set.is_some() =>
error::set_again(name),
let &NameValue(_, Lit::Int(val, ty)) = meta,
val <= u32::MAX as u64,
is_unsigned(ty) =>
*set = Some(val as u32),
else =>
error::weight_malformed(name),
} I see a few problems with your and my version of your syntax - hopefully you can address them? :
|
For comparison: if expr.node is ExprAddrOf(_, ref expr)
&& expr.node is ExprArray(ref exprs)
&& exprs.last() is Some(expr)
&& expr.node is ExprLit(ref lit)
&& lit.node is LitKind::Str(ref lit, _) {
Some((lit.as_str(), exprs.len()))
} else {
None
} if let ExprAddrOf(_, ref expr) = expr.node
, let ExprArray(ref exprs) = expr.node
, let Some(expr) = exprs.last()
, let ExprLit(ref lit) = expr.node
, let LitKind::Str(ref lit, _) = lit.node {
Some((lit.as_str(), exprs.len()))
} else {
None
} |
@Centril I don't really want to derail this RFC thread into discussing That said
val x = if (cond)
bar
else
baz
vs
val x = when {
cond -> bar
else -> baz
} |
Yeah, I'd probably write this too with less/greater and some other operators, but pattern matching is
It is very much desirable for non-exhaustive pattern matching construction to be an expression, so it's not confusion, it's correct understanding. (Also, the
It's the order in
Can't say much about alignment. rustfmt have standard rules for
Let's look at these function for example https://github.com/rust-lang/rust/blob/master/src/librustc/ty/sty.rs#L1263-L1518
Yes, |
You are not derailing, it was an interesting proposal. There's having one way, which I am not a proponent of, and there's having 10 ways, which I am not a proponent of either, and somewhere you have to strike a balance... ;)
No matter, it is what we got... so don't we have to be consistent with the inconsistency of
match expr_on {
pat_1 => expr_1,
...
} Here,
Sounds reasonable. You've convinced me on this point =) However, |
You've all made great points. I don't feel very strongly about the particular syntax proposed in the RFC, so at this point I am certainly willing to rework this RFC, with some help, into either |
Fwiw, as written this rfc seems to follow the ideas in #929 fairly closely and while 'is' was mentioned in that issue, it was relatively recently added in the thread and easy to miss. As such, it isn't surprising it wasn't included already in the rfc as a variant or option. A positive note of both these is they both appear to make the language consistent and more general which is a huge improvement over the current situation in which 'if let' is considered an eyesore. Having said that, the greater potential benefit of the 'is' is pretty cool. Something I didn't see in this rfc is the inclusion of 'where' as seen in the following example taken from #929 (comment) : if let a = foo(), let b = a.bar(), let c = b.baz() where c > 2, let d = c.quux() {
// all operations succeeded
} else {
// something failed
} |
@mdinger I think we just forgot about it when writing about alternatives because there are so many potential syntactic variants, you have to stop somewhere ;) With respect to |
I'm in favor of chaining And sure, you could say that an The whole |
My problem with if opt_x is Some(x) && x > 10 {
println!("{}", x);
} there are just so many tiny changes that make it no longer work: if opt_x is Some(x) || x > 10 {
if (b == opt_x is Some(x)) && x > 10 {
if foo(opt_x is Some(x)) && x > 10 { I'm scared just thinking of how to write a thorough but non-surprising rule here. Aside: I prefer I think I'm personally coming around to @est31's "if guards on ifs". I like that it would extend to |
It's interesting to compare it with Kotlin, which also uses The differences is that instead of destructing, Kotlin's However, as long as the compiler does all the inference work for you, actually using this feature is easy: you don't have to replay the analysis in your head when reading or writing code, because the compiler catches all errors. |
I think |
I disagree that this behavior should be called weird, this is essentially control-flow sensitive typechecking, which is more or less mainstream today, and can be found in C#, Kotlin and TypeScript. |
@matklad Do you have some relevant papers I could read on this topic perhaps? Might be useful when rewriting the RFC if we decide to go in this direction. PS: You have some private messages on IRC ;) |
As far as I can see, C#, Kotlin and TypeScript (and other languages I've heard of) all have only flow-sensitive type "refinement", rather than type-dependent bindings. The typing judgements may look similar or identical for both, but the implementation in a compiler will be different (name resolution is affected, not just type checking) and the UX may be rather different as well. It certainly feels different to me, and I'm perfectly comfortable with the idea of flow-sensitive typing. |
@Centril I hope you won't just blindly take the results of this poll and go with whatever was most popular? This was never what the poll was about. |
Certainly not blindly. I would put it this way: Other proposals now have a somewhat increased burden of proof to show why the reasons put forth by the majority, such as "this is more consistent with current Rust" are less important than the reasons put forth by the minority positions. The reasons for picking the majority position as well as not picking the minority ones seem well argued by the respondents. |
The poll was made in a way where no sum up of the arguments and the downsides and upsides and hidden gotchas of the various proposals was provided. This means that the people filling out the questions weren't able to make an informed descision unless they thought about those themselves, and I'm sure that most just answered basing on their gut feeling. Even if you provide the information, many people might not read it, so basing decisions on polls is no good solution. The proposal provided very interesting arguments like e.g. saying that Also, note that the least disliked option is to do nothing. Either way, there hasn't been a single involvement by the lang team on this. What are their opinions on the matter? Which option is best for them? |
I've read at least 300 of the answers, and we can cherry pick answers, but my feeling is that a lot of them were informed and good comments.
That's a misinterpretation of the meaning of disliking none of them. Disliking none of them means that you can live with any of the 6 syntaxes proposed. At least that is what the question/answer intended. |
Reading through the summary, this comment (I didn't look from whom) stood out to me:
I think my wish right now would be to
|
As this RFC is written today, it is wholly insufficient to cover alternatives and discuss the pros and cons. The survey has also highlighted may arguments for and against each alternative. The guide-level-explanation might also be changed to use a different syntax. Furthermore, this RFC has had Given all this, I think the best course of action is to close the RFC. I will be doing so in a day unless there are major objections from folks. |
I'm preparing a prototype for in-expression bindings right now, btw. Many (EDIT: but not all!) comments against |
@petrochenkov The keyword
My only nit is that |
I do prefer I also agree with @scottmcm that |
This is just bad wording on my side, I didn't mean it's a summary for all reactions, just the most popular concern. |
Closing as promised. |
So, I finally completed my prototype of non-exhaustive pattern-matching expressions, including in-expression bindings. Some notes:
let x;
match Some(10) {
Some(..) | None => {
x = 11;
}
_ => {} // Unreachable
}
let y = x; // Currently ERROR: use of possibly uninitialized variable: `x`
|
My suggestion about
|
…shtriplett Stabilize `let_chains` in Rust 1.64 # Stabilization proposal This PR proposes the stabilization of `#![feature(let_chains)]` in a future-compatibility way that will allow the **possible** addition of the `EXPR is PAT` syntax. Tracking issue: rust-lang#53667 Version: 1.64 (beta => 2022-08-11, stable => 2022-10-22). ## What is stabilized The ability to chain let expressions along side local variable declarations or ordinary conditional expressions. For example: ```rust pub enum Color { Blue, Red, Violet, } pub enum Flower { Rose, Tulip, Violet, } pub fn roses_are_red_violets_are_blue_printer( (first_flower, first_flower_color): (Flower, Color), (second_flower, second_flower_color): (Flower, Color), pick_up_lines: &[&str], ) { if let Flower::Rose = first_flower && let Color::Red = first_flower_color && let Flower::Violet = second_flower && let Color::Blue = second_flower_color && let &[first_pick_up_line, ..] = pick_up_lines { println!("Roses are red, violets are blue, {}", first_pick_up_line); } } fn main() { roses_are_red_violets_are_blue_printer( (Flower::Rose, Color::Red), (Flower::Violet, Color::Blue), &["sugar is sweet and so are you"], ); } ``` ## Motivation The main motivation for this feature is improving readability, ergonomics and reducing paper cuts. For more examples, see the [RFC](https://github.com/rust-lang/rfcs/blob/master/text/2497-if-let-chains.md). ## What isn't stabilized * Let chains in match guards (`if_let_guard`) * Resolution of divergent non-terminal matchers * The `EXPR is PAT` syntax ## History * On 2017-12-24, [RFC: if- and while-let-chains](rust-lang/rfcs#2260) * On 2018-07-12, [eRFC: if- and while-let-chains, take 2](rust-lang/rfcs#2497) * On 2018-08-24, [Tracking issue for eRFC 2497, "if- and while-let-chains, take 2](rust-lang#53667) * On 2019-03-19, [Run branch cleanup after copy prop](rust-lang#59290) * On 2019-03-26, [Generalize diagnostic for x = y where bool is the expected type](rust-lang#59439) * On 2019-04-24, [Introduce hir::ExprKind::Use and employ in for loop desugaring](rust-lang#60225) * On 2019-03-19, [[let_chains, 1/6] Remove hir::ExprKind::If](rust-lang#59288) * On 2019-05-15, [[let_chains, 2/6] Introduce Let(..) in AST, remove IfLet + WhileLet and parse let chains](rust-lang#60861) * On 2019-06-20, [[let_chains, 3/6] And then there was only Loop](rust-lang#61988) * On 2020-11-22, [Reintroduce hir::ExprKind::If](rust-lang#79328) * On 2020-12-24, [Introduce hir::ExprKind::Let - Take 2](rust-lang#80357) * On 2021-02-19, [Lower condition of if expression before it's "then" block](rust-lang#82308) * On 2021-09-01, [Fix drop handling for `if let` expressions](rust-lang#88572) * On 2021-09-04, [Formally implement let chains](rust-lang#88642) * On 2022-01-19, [Add tests to ensure that let_chains works with if_let_guard](rust-lang#93086) * On 2022-01-18, [Introduce `enhanced_binary_op` feature](rust-lang#93049) * On 2022-01-22, [Fix `let_chains` and `if_let_guard` feature flags](rust-lang#93213) * On 2022-02-25, [Initiate the inner usage of `let_chains`](rust-lang#94376) * On 2022-01-28, [[WIP] Introduce ast::StmtKind::LetElse to allow the usage of `let_else` with `let_chains`](rust-lang#93437) * On 2022-02-26, [1 - Make more use of `let_chains`](rust-lang#94396) * On 2022-02-26, [2 - Make more use of `let_chains`](rust-lang#94400) * On 2022-02-27, [3 - Make more use of `let_chains`](rust-lang#94420) * On 2022-02-28, [4 - Make more use of `let_chains`](rust-lang#94445) * On 2022-02-28, [5 - Make more use of `let_chains`](rust-lang#94448) * On 2022-02-28, [6 - Make more use of `let_chains`](rust-lang#94465) * On 2022-03-01, [7 - Make more use of `let_chains`](rust-lang#94476) * On 2022-03-01, [8 - Make more use of `let_chains`](rust-lang#94484) * On 2022-03-01, [9 - Make more use of `let_chains`](rust-lang#94498) * On 2022-03-08, [Warn users about `||` in let chain expressions](rust-lang#94754) From the first RFC (2017-12-24) to the theoretical future stabilization day (2022-10-22), it can be said that this feature took 4 years, 9 months and 28 days of research, development, discussions, agreements and headaches to be settled. ## Divergent non-terminal matchers More specifically, rust-lang#86730. ```rust macro_rules! mac { ($e:expr) => { if $e { true } else { false } }; } fn main() { // OK! assert_eq!(mac!(true && let 1 = 1), true); // ERROR! Anything starting with `let` is not considered an expression assert_eq!(mac!(let 1 = 1 && true), true); } ``` To the best of my knowledge, such error or divergence is orthogonal, does not prevent stabilization and can be tackled independently in the near future or effectively in the next Rust 2024 edition. If not, then https://github.com/c410-f3r/rust/tree/let-macro-blah contains a set of changes that will consider `let` an expression. It is possible that none of the solutions above satisfies all applicable constraints but I personally don't know of any other plausible answers. ## Alternative syntax Taking into account the usefulness of this feature and the overwhelming desire to use both now and in the past, `let PAT = EXPR` will be utilized for stabilization but it doesn't or shall create any obstacle for a **possible** future addition of `EXPR is PAT`. The introductory snippet would then be written as the following. ```rust if first_flower is Flower::Rose && first_flower_color is Color::Red && second_flower is Flower::Violet && second_flower_color is Color::Blue && pick_up_lines is &[first_pick_up_line, ..] { println!("Roses are red, violets are blue, {}", first_pick_up_line); } ``` Just to reinforce, this PR only unblocks a **possible** future road for `EXPR is PAT` and does emphasize what is better or what is worse. ## Tests * [Verifies the drop order of let chains and ensures it won't change in the future in an unpredictable way](https://github.com/rust-lang/rust/blob/master/src/test/ui/mir/mir_let_chains_drop_order.rs) * [AST lowering does not wrap let chains in an `DropTemps` expression](https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/ast-lowering-does-not-wrap-let-chains.rs) * [Checks pretty printing output](https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/ast-pretty-check.rs) * [Verifies uninitialized variables due to MIR modifications](https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/chains-without-let.rs) * [A collection of statements where `let` expressions are forbidden](https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/disallowed-positions.rs) * [All or at least most of the places where let chains are allowed](https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/feature-gate.rs) * [Ensures that irrefutable lets are allowed in let chains](https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/irrefutable-lets.rs) * [issue-88498.rs](https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/issue-88498.rs), [issue-90722.rs](https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/issue-90722.rs), [issue-92145.rs](https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/issue-92145.rs) and [issue-93150.rs](https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/issue-93150.rs) were bugs found by third parties and fixed overtime. * [Indexing was triggering a ICE due to a wrongly constructed MIR graph](https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/no-double-assigments.rs) * [Protects the precedence of `&&` in relation to other things](https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/protect-precedences.rs) * [`let_chains`, as well as `if_let_guard`, has a valid MIR graph that evaluates conditional expressions correctly](https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/then-else-blocks.rs) Most of the infra-structure used by let chains is also used by `if` expressions in stable compiler versions since rust-lang#80357 and rust-lang#88572. As a result, no bugs were found since the integration of rust-lang#88642. ## Possible future work * Let chains in match guards is implemented and working but stabilization is blocked by `if_let_guard`. * The usage of `let_chains` with `let_else` is possible but not implemented. Regardless, one attempt was introduced and closed in rust-lang#93437. Thanks `@Centril` for creating the RFC and huge thanks (again) to `@matthewjasper` for all the reviews, mentoring and MIR implementations. Fixes rust-lang#53667
Stabilize `let_chains` in Rust 1.64 # Stabilization proposal This PR proposes the stabilization of `#![feature(let_chains)]` in a future-compatibility way that will allow the **possible** addition of the `EXPR is PAT` syntax. Tracking issue: #53667 Version: 1.64 (beta => 2022-08-11, stable => 2022-10-22). ## What is stabilized The ability to chain let expressions along side local variable declarations or ordinary conditional expressions. For example: ```rust pub enum Color { Blue, Red, Violet, } pub enum Flower { Rose, Tulip, Violet, } pub fn roses_are_red_violets_are_blue_printer( (first_flower, first_flower_color): (Flower, Color), (second_flower, second_flower_color): (Flower, Color), pick_up_lines: &[&str], ) { if let Flower::Rose = first_flower && let Color::Red = first_flower_color && let Flower::Violet = second_flower && let Color::Blue = second_flower_color && let &[first_pick_up_line, ..] = pick_up_lines { println!("Roses are red, violets are blue, {}", first_pick_up_line); } } fn main() { roses_are_red_violets_are_blue_printer( (Flower::Rose, Color::Red), (Flower::Violet, Color::Blue), &["sugar is sweet and so are you"], ); } ``` ## Motivation The main motivation for this feature is improving readability, ergonomics and reducing paper cuts. For more examples, see the [RFC](https://github.com/rust-lang/rfcs/blob/master/text/2497-if-let-chains.md). ## What isn't stabilized * Let chains in match guards (`if_let_guard`) * Resolution of divergent non-terminal matchers * The `EXPR is PAT` syntax ## History * On 2017-12-24, [RFC: if- and while-let-chains](rust-lang/rfcs#2260) * On 2018-07-12, [eRFC: if- and while-let-chains, take 2](rust-lang/rfcs#2497) * On 2018-08-24, [Tracking issue for eRFC 2497, "if- and while-let-chains, take 2](rust-lang/rust#53667) * On 2019-03-19, [Run branch cleanup after copy prop](rust-lang/rust#59290) * On 2019-03-26, [Generalize diagnostic for x = y where bool is the expected type](rust-lang/rust#59439) * On 2019-04-24, [Introduce hir::ExprKind::Use and employ in for loop desugaring](rust-lang/rust#60225) * On 2019-03-19, [[let_chains, 1/6] Remove hir::ExprKind::If](rust-lang/rust#59288) * On 2019-05-15, [[let_chains, 2/6] Introduce Let(..) in AST, remove IfLet + WhileLet and parse let chains](rust-lang/rust#60861) * On 2019-06-20, [[let_chains, 3/6] And then there was only Loop](rust-lang/rust#61988) * On 2020-11-22, [Reintroduce hir::ExprKind::If](rust-lang/rust#79328) * On 2020-12-24, [Introduce hir::ExprKind::Let - Take 2](rust-lang/rust#80357) * On 2021-02-19, [Lower condition of if expression before it's "then" block](rust-lang/rust#82308) * On 2021-09-01, [Fix drop handling for `if let` expressions](rust-lang/rust#88572) * On 2021-09-04, [Formally implement let chains](rust-lang/rust#88642) * On 2022-01-19, [Add tests to ensure that let_chains works with if_let_guard](rust-lang/rust#93086) * On 2022-01-18, [Introduce `enhanced_binary_op` feature](rust-lang/rust#93049) * On 2022-01-22, [Fix `let_chains` and `if_let_guard` feature flags](rust-lang/rust#93213) * On 2022-02-25, [Initiate the inner usage of `let_chains`](rust-lang/rust#94376) * On 2022-01-28, [[WIP] Introduce ast::StmtKind::LetElse to allow the usage of `let_else` with `let_chains`](rust-lang/rust#93437) * On 2022-02-26, [1 - Make more use of `let_chains`](rust-lang/rust#94396) * On 2022-02-26, [2 - Make more use of `let_chains`](rust-lang/rust#94400) * On 2022-02-27, [3 - Make more use of `let_chains`](rust-lang/rust#94420) * On 2022-02-28, [4 - Make more use of `let_chains`](rust-lang/rust#94445) * On 2022-02-28, [5 - Make more use of `let_chains`](rust-lang/rust#94448) * On 2022-02-28, [6 - Make more use of `let_chains`](rust-lang/rust#94465) * On 2022-03-01, [7 - Make more use of `let_chains`](rust-lang/rust#94476) * On 2022-03-01, [8 - Make more use of `let_chains`](rust-lang/rust#94484) * On 2022-03-01, [9 - Make more use of `let_chains`](rust-lang/rust#94498) * On 2022-03-08, [Warn users about `||` in let chain expressions](rust-lang/rust#94754) From the first RFC (2017-12-24) to the theoretical future stabilization day (2022-10-22), it can be said that this feature took 4 years, 9 months and 28 days of research, development, discussions, agreements and headaches to be settled. ## Divergent non-terminal matchers More specifically, rust-lang/rust#86730. ```rust macro_rules! mac { ($e:expr) => { if $e { true } else { false } }; } fn main() { // OK! assert_eq!(mac!(true && let 1 = 1), true); // ERROR! Anything starting with `let` is not considered an expression assert_eq!(mac!(let 1 = 1 && true), true); } ``` To the best of my knowledge, such error or divergence is orthogonal, does not prevent stabilization and can be tackled independently in the near future or effectively in the next Rust 2024 edition. If not, then https://github.com/c410-f3r/rust/tree/let-macro-blah contains a set of changes that will consider `let` an expression. It is possible that none of the solutions above satisfies all applicable constraints but I personally don't know of any other plausible answers. ## Alternative syntax Taking into account the usefulness of this feature and the overwhelming desire to use both now and in the past, `let PAT = EXPR` will be utilized for stabilization but it doesn't or shall create any obstacle for a **possible** future addition of `EXPR is PAT`. The introductory snippet would then be written as the following. ```rust if first_flower is Flower::Rose && first_flower_color is Color::Red && second_flower is Flower::Violet && second_flower_color is Color::Blue && pick_up_lines is &[first_pick_up_line, ..] { println!("Roses are red, violets are blue, {}", first_pick_up_line); } ``` Just to reinforce, this PR only unblocks a **possible** future road for `EXPR is PAT` and does emphasize what is better or what is worse. ## Tests * [Verifies the drop order of let chains and ensures it won't change in the future in an unpredictable way](https://github.com/rust-lang/rust/blob/master/src/test/ui/mir/mir_let_chains_drop_order.rs) * [AST lowering does not wrap let chains in an `DropTemps` expression](https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/ast-lowering-does-not-wrap-let-chains.rs) * [Checks pretty printing output](https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/ast-pretty-check.rs) * [Verifies uninitialized variables due to MIR modifications](https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/chains-without-let.rs) * [A collection of statements where `let` expressions are forbidden](https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/disallowed-positions.rs) * [All or at least most of the places where let chains are allowed](https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/feature-gate.rs) * [Ensures that irrefutable lets are allowed in let chains](https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/irrefutable-lets.rs) * [issue-88498.rs](https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/issue-88498.rs), [issue-90722.rs](https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/issue-90722.rs), [issue-92145.rs](https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/issue-92145.rs) and [issue-93150.rs](https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/issue-93150.rs) were bugs found by third parties and fixed overtime. * [Indexing was triggering a ICE due to a wrongly constructed MIR graph](https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/no-double-assigments.rs) * [Protects the precedence of `&&` in relation to other things](https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/protect-precedences.rs) * [`let_chains`, as well as `if_let_guard`, has a valid MIR graph that evaluates conditional expressions correctly](https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/then-else-blocks.rs) Most of the infra-structure used by let chains is also used by `if` expressions in stable compiler versions since rust-lang/rust#80357 and rust-lang/rust#88572. As a result, no bugs were found since the integration of rust-lang/rust#88642. ## Possible future work * Let chains in match guards is implemented and working but stabilization is blocked by `if_let_guard`. * The usage of `let_chains` with `let_else` is possible but not implemented. Regardless, one attempt was introduced and closed in rust-lang/rust#93437. Thanks `@Centril` for creating the RFC and huge thanks (again) to `@matthewjasper` for all the reviews, mentoring and MIR implementations. Fixes #53667
Rendered
This RFC was co-authored with @scottmcm ;)
Here's a survey on some choices of syntax.
Please answer it if you have the time.
And please don't spread the survey around.
https://goo.gl/forms/Wx61sx9s03N5SGA52
Before considering this RFC, please remember what is most important: reducing rightward drift and improving ergonomics in control flow. The exact syntax can always be changed into a direction you prefer. If you don't like the syntax, please write a comment about that below or agree with someone who has put forth your view. Thank you :)
EDIT: The exact syntax regarding
&&
or,
or chainingif
is very much in flux right now.This RFC extends
if let
- andwhile let
-expressions with chaining, allowingyou to combining multiple
let
s and conditions together naturally. With thisRFC implemented, you will, among other things, now be able to write: