Skip to content
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

Closed
wants to merge 1 commit into from

Conversation

Centril
Copy link
Contributor

@Centril Centril commented Dec 24, 2017

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 chaining if is very much in flux right now.


This RFC extends if let- and while let-expressions with chaining, allowing
you to combining multiple lets and conditions together naturally. With this
RFC implemented, you will, among other things, now be able to write:

/// Returns the slice of format string parts in an `Arguments::new_v1` call.
fn get_argument_fmtstr_parts(expr: &Expr) -> Option<(InternedString, usize)> {
    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 Centril added the T-lang Relevant to the language team, which will review and decide on the RFC. label Dec 24, 2017
@est31
Copy link
Member

est31 commented Dec 24, 2017

RFC 2046 is a more general control flow graph (CFG) control feature. While it doesn't solve the rightward drift or ergonomic issues that this RFC does, it allows the macros to be improved by removing duplication of else blocks.

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, unwrap_or_none, you can simply it to:

'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.

@Havvy
Copy link
Contributor

Havvy commented Dec 24, 2017

With this, we don't need && anymore.

// This predicate
if a && b && c {}

// can be replaced with this if let chain.
if let () = (), a, b, c {}

The () = () is required because the chain has to start with a pattern.

@est31
Copy link
Member

est31 commented Dec 24, 2017

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.

@Wyverald
Copy link

I find this syntax very confusing. At a glance this looks very much like parallel if lets, instead of sequential ones -- I was expecting all these if lets to be applied at the same time. But that was obviously not the case as expr.node was being matched against multiple disjoint patterns, so I figured "oh, it must be an or instead of an and" -- and no, it's not that either. (Thankfully -- an or-ing if let chain would be even more troublesome.)

I still do think the best way to deal with this kind of right-ward drift is with early returns, with something like if !let or let pat = expr else. The pros and cons of that approach are extensively discussed in another RFC, so I won't reiterate everything here.

@est31
Copy link
Member

est31 commented Dec 24, 2017

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 if like guards is more clear here.

@Centril
Copy link
Contributor Author

Centril commented Dec 24, 2017

@Havvy You certainely could write if let () = (), a, b, c {}, but also if a, b, c {} - but I don't know if I would recommend it for readability purposes.

@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 'label 5 times. I am a firm believer that control flow should be more universal to all rustaceans and not mainly done by macro (we have if_chain, and that works, but I believe we should have this in surface lang instead). Macros are better, in my opinion for things like EDSLs.

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.

@Centril
Copy link
Contributor Author

Centril commented Dec 24, 2017

@est31 If we go with your proposed syntax however, we need to consider what that means for | and what the precedence between | and if is for future-proofing purposes. Any thoughts on this?

@est31
Copy link
Member

est31 commented Dec 24, 2017

But the first non-macro snippet you listed is not very ergonomic and has a lot of repetition.

Yeah, that's why I added a macro snippet. The first snippet was only to reduce rightward drift.

The macro does reduce this, but repeats 'label 5 times.

You can probably minimize this further by creating a macro inside the 'label block that passes on 'label. Its only an improvement for very long examples though.

If we go with your proposed syntax however, we need to consider what that means for | and what the precedence between | and if is for future-proofing purposes. Any thoughts on this?

I don't have any views/thoughts on the precedence.

@Centril
Copy link
Contributor Author

Centril commented Dec 24, 2017

Yeah, that's why I added a macro snippet. The first snippet was only to reduce rightward drift.

Right. I will update the RFC with a more nuanced description in a while (holidays, so, yeah... ^,^)

You can probably minimize this further by creating a macro inside the 'label block that passes on 'label. Its only an improvement for very long examples though.

Sounds about right. However, the macro is specific to returning None, does the macro method scale to else if and other expressions of other types in else ?

I don't have any views/thoughts on the precedence.

We'll have to resolve this together tho I think unless we're OK with shutting the door to if let A(x) = e1 | B(x) = e1 {..} else {..} (semantically).

@mdinger
Copy link
Contributor

mdinger commented Dec 24, 2017

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.

@petrochenkov
Copy link
Contributor

petrochenkov commented Dec 24, 2017

Short review (I'll probably elaborate later):

  • I support the motivation with all my heart. The current situation is bad.
  • This RFC solves the problem of composability of conditions, but doesn't solve the problem of inverted order. Nobody writes 5 == x in their non-pattern conditions, but analogues of if let 5 = x are for some reason okay in conditions with patterns. I want to write x op 5 in both pattern and pattern-less conditions.
  • This RFC doesn't solve the problem of let not being an expression either. Have you ever seen something like if let VariantX(..) = self { true } else { false }? It's so bad people create separate functions like
    fn is_variant_x(self) {
        if let VariantX(..) = self { true } else { false }
    }
    or generate unnecessary PartialEq impls with derive just to avoid writing it more than once.
  • The logical AND is called , for some reason, I thought && is usually used for it.
  • This is a lot of ad-hoc syntax to deprecate when the proper solution solving all the listed problems is implemented :)
    The example from the motivation section rewritten with is:
    let name = meta.name();
    if set.is_some() {
        error::set_again(name);
    } else if meta is &NameValue(_, Lit::Int(val, ty)) &&
              val <= u32::MAX as u64 &&
              is_unsigned(ty) {
        *set = Some(val as u32); 
    } else {
        error::weight_malformed(name);
    }

Before considering this RFC, please remember what is most important: semantics. The syntax can always be changed into a direction you prefer.

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.

@matklad
Copy link
Member

matklad commented Dec 24, 2017

@petrochenkov ohhh, this is looks sweet! Could we also have a cond/when/multibranch if :) ?

let name = meta.name();
when {
    set.is_some() => 
        error::set_again(name),

    meta is &NameValue(_, Lit::Int(val, ty)) && 
    val <= u32::MAX as u64 && 
    is_unsigned(ty) => 
        *set = Some(val as u32),

    else => 
        error::wiegt_malformed(name),
}

@Centril
Copy link
Contributor Author

Centril commented Dec 24, 2017

@petrochenkov

Nobody writes 5 == x in their non-pattern conditions

I am sure I have written that way on many occasions.
In particular, I would write: 5 < x && x < 10 and never x > 5 && x < 10.

The logical AND is called , for some reason, I thought && is usually used for it.

I think the reasons we state in the RFC for rejecting this are valid.
If , is not a liked syntax we should go with @est31's proposal instead in my opinion.

meta is &NameValue(_, Lit::Int(val, ty))

I am not a fan of this syntax. It is inconsistent with the order already used in if let (the reverse). We made a decision way back to have a certain order (pattern first) and I think we should stick with it.

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 is also does not feel accurate - matches would be better but that is too long. I'd counter with if meta like &NameValue(_, Lit::Int(val, ty)) but that feels weird too.

I'm assuming that meta is &NameValue(_, Lit::Int(val, ty)) also is an expression on its own that can be used outside of if - is that accurate?

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.

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.

@Centril
Copy link
Contributor Author

Centril commented Dec 24, 2017

@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? :

  • We are essentially deprecating if / else if / else (or we should, or we have an abundance of too many ways to do it)
  • If Rust was going 1.0 today I would probably buy this syntax right off the bat. After 1.0, this feels like radical change - and while I'm quite the revolutionary, I am not in language design and syntax. Should we not be more incremental?
  • You have introduced one level of indent and rightward drift - it is a constant factor and won't indent further, but it is one thing that makes me reluctant to use match today.

@Centril
Copy link
Contributor Author

Centril commented Dec 24, 2017

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
    }

@matklad
Copy link
Member

matklad commented Dec 24, 2017

@Centril I don't really want to derail this RFC thread into discussing cond, I just want to make sure it's on the radar when we discuss pattern matching improvements :)

That said

  1. cond is important because programmers make errors in if conditions, so simple improvements in this area are great.

  2. I won't be too worried about orthogonality here: "one way to do it" is not a design principle of the Rust language, and the logic for conditions is already not quite orthogonal.

  3. indentation and in general formatting are indeed a problem here. It's fun that in Kotlin, which have both if/else and when, some people use pattern matching on boolean just because it looks neater:

val x = if (cond) 
    bar
else
    baz

vs

val x = when {
  cond -> bar
  else -> baz
}

@petrochenkov
Copy link
Contributor

petrochenkov commented Dec 24, 2017

@Centril

In particular, I would write: 5 < x && x < 10 and never x > 5 && x < 10.

Yeah, I'd probably write this too with less/greater and some other operators, but pattern matching is == (maybe != in the future). I haven't seen people using == like this since very old times before C compilers started issuing basic warnings.

I think the reasons we state in the RFC for rejecting this are valid.

Using && in the syntax leads the the obvious "well, what about ||?"
It also causes confusion with && being part of the expression.
The syntax gives the impression that let is now an expression, which it isn't.

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 EXPR is PATTERN expression syntax can be added to the language backward compatibly (with same priority as EXPR as TYPE), so it's not a problem technically).
Since EXPR is PATTERN is a usual boolean expression, || can be naturally supported as well (precise scoping rules for bindings need to be worked out though).

I am not a fan of this syntax. It is inconsistent with the order already used in if let (the reverse). We made a decision way back to have a certain order (pattern first) and I think we should stick with it.

It's the order in if let that's inconsistent - match uses expression on the left and patterns on the right.
Swapping the order when rewriting match as if let and vice versa is one of primary if let annoyances that is is supposed to fix.
If is is implemented, I'd be happy to never see if let/while let in my code again.

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.

Can't say much about alignment. rustfmt have standard rules for ifs and conditions and I'm okay with them.

The keyword is also does not feel accurate - matches would be better but that is too long.

Let's look at these function for example https://github.com/rust-lang/rust/blob/master/src/librustc/ty/sty.rs#L1263-L1518
They are used to shorten non-exhaustive match expressions that we are discussing and start with is_.
fn is_some() and fn is_none()on Option aren't called matches_some/matches_none as well.
It seems natural to turns this from naming convention into an actual operator (x.is_some() -> x is Some(..)).
Oh, and the syntax is borrowed from C# pattern matching, so at least I'm not alone in thinking it's appropriate.

I'm assuming that meta is &NameValue(_, Lit::Int(val, ty)) also is an expression on its own that can be used outside of if - is that accurate?

Yes, expr is pat is a normal boolean expression that can be used in any contexts (so if let pat = expr { true } else { false } is purged from existence forever.). That's one of the Three Pillars of the feature in addition to composability and order :)
(Again, precise scoping rules for bindings still need to be worked out. I never submitted this as an RFC because I wanted to make a PoC implementation first, but never had enough time).

@Centril
Copy link
Contributor Author

Centril commented Dec 24, 2017

@matklad I won't be too worried about orthogonality here: "one way to do it" is not a design principle of the Rust language, and the logic for conditions is already not quite orthogonal.

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... ;)

@petrochenkov

It's the order in if let that's inconsistent

No matter, it is what we got... so don't we have to be consistent with the inconsistency of if let?

match uses expression on the left and patterns on the right.

match expr_on {
    pat_1 => expr_1,
    ...
}

Here, pat_1 is to the left. If you write match expr_on { pat_1 => expr_1 } you can make the case that pat_1 is on the right, but I don't buy this.

It seems natural to turns this from naming convention into an actual operator (x.is_some() -> x is Some(..)).

Sounds reasonable. You've convinced me on this point =) However, x.is_some() will still have its place.

@Centril
Copy link
Contributor Author

Centril commented Dec 24, 2017

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 when or EXPR is PATTERN or both. Perhaps then we should do EXPR is PATTERN first and then when later since they seem to be compatible. What do you think about this @scottmcm ?

@mdinger
Copy link
Contributor

mdinger commented Dec 24, 2017

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
}

@Centril
Copy link
Contributor Author

Centril commented Dec 24, 2017

@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 where it seems redundant to me, and aligns poorly. Also, I've never considered if let an eyesore - just clunky when you have to match on more.

@Havvy
Copy link
Contributor

Havvy commented Dec 25, 2017

I'm in favor of chaining if let as per this RFC over an is/matches operator mainly due to the fact that as an expression, is and matches cannot introduce new bindings outside of the expression without being extremely inconsistent with the rest of the language. E.g. a is B(c) && c.is_foo() should be illegal (and then you'd write it as a is B(_) so as to not introduce any bindings). With if let, the binding is a-okay because it's only valid in a subexpression of the main if-let expression.

And sure, you could say that an is expression in the predicate portion of an if expression can introduce new bindings when used with an is expression, but that's really special casing the semantics in a bad way.


The whole pattern = expr thing in if let is also not inconsistent with the language. Let statements are the same way. Function parameters are pattern : type but if we add default arguments, it'd probably be pattern: type = expr or something of the same order. Only match is expr { pattern => expr, ... }.

@scottmcm
Copy link
Member

My problem with is-that-can-bind is that whether the binding should be in scope is arbitrarily complicated, up to incomputable. If I take the example from there:

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 is to replace all those common methods like .is_some() that don't introduce bindings, so you can x is Some(_) or c is 'a'...'z' or x is Err(ErrorKind::FooBar) (without ==).

I think I'm personally coming around to @est31's "if guards on ifs". I like that it would extend to match naturally, if you think of if as a pattern suffix and just keep adding more and more if or if let suffixes. So then the if expression is just a single if guard, in a sense, with special else support.

@matklad
Copy link
Member

matklad commented Dec 25, 2017

My problem with is-that-can-bind is that whether the binding should be in scope is arbitrarily complicated, up to incomputable.

It's interesting to compare it with Kotlin, which also uses is operator for the similar purpose: https://kotlinlang.org/docs/reference/typecasts.html#smart-casts.

The differences is that instead of destructing, Kotlin's is supplies a flow-sensitive type information. The compiler indeed uses pretty smart control-flow analysis to check if every use of a variable is dominated by the is check.

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.

@est31
Copy link
Member

est31 commented Dec 25, 2017

I think is-that-can't-bind is, while it would require a new (at least contextual) keyword, a great addition. Not so sure about is-that-can-bind, due to the weird behaviour that this would create (see @scottmcm 's comment above).

@matklad
Copy link
Member

matklad commented Dec 25, 2017

due to the weird behaviour that this would create

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.

@Centril
Copy link
Contributor Author

Centril commented Dec 25, 2017

@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 ;)

@hanna-kruppe
Copy link

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.

@est31
Copy link
Member

est31 commented Jan 6, 2018

@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.

@Centril
Copy link
Contributor Author

Centril commented Jan 6, 2018

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.

@est31
Copy link
Member

est31 commented Jan 6, 2018

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 , might confuse C coders because , here means execution where the result is discarded, and (quoting an argument against my proposal) if guards might confuse C coders as well because they might think now that in Rust, if doesn't need any braces while in fact it does. Another comment brought up if let a = b && c parsing ambiguities. How many people knew about all of these arguments?

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?

@Centril
Copy link
Contributor Author

Centril commented Jan 6, 2018

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.

Also, note that the least disliked option is to do nothing.

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.

@scottmcm
Copy link
Member

scottmcm commented Jan 6, 2018

Reading through the summary, this comment (I didn't look from whom) stood out to me:

In the end I'm very torn on whether any of this is "good enough". All of the proposals seem to come with unintuitive caveats - either the syntax feels unintuitive or the implications are unintuitive.

I think my wish right now would be to

  1. Postpone this for now,
  2. Accept RFC: label-break-value #2046 so macros can have better codegen while experimenting with what the syntax should be for this, and
  3. Revisit the problem space of Add a let...else expression, similar to Swift's guard let...else #1303, as most of my chaining uses would be adequately handled by that.

@Centril
Copy link
Contributor Author

Centril commented Jan 6, 2018

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 >= 150 comments, so changes to the proposal may be highly confusing for a reader.

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.

@petrochenkov
Copy link
Contributor

petrochenkov commented Jan 6, 2018

I'm preparing a prototype for in-expression bindings right now, btw.
But I guess it won't disappear anywhere if the RFC is closed.


Many (EDIT: but not all!) comments against EXPR is PAT can be summarized as "New keyword. Scary. Unnecessary.".
Since more popular let PAT = EXPR cannot be reused for technical reasons (at least not without a bunch of compatibility hacks), I wonder if EXPR match PAT can address these concerns. It reuses an existing non-scary and necessary keyword, can be taught as a short "inline" form of full match, and the operand order is consistent with full match as well.

@Centril
Copy link
Contributor Author

Centril commented Jan 6, 2018

@petrochenkov The keyword match makes me much more comfortable. It addresses among other things:

  • Not confusing python users with is.
  • Not confusing readers with as.
  • It is semantically clearer and probably more correct since an is that introduces bindings is weird.

My only nit is that match has the wrong tense, but I can certainly live with that. Using matches would be too long, decrease familiarity, and introduce a new keyword.

@rpjohnst
Copy link

rpjohnst commented Jan 6, 2018

I do prefer EXPR match PAT to the is version, but I don't think the reaction can be fairly summarized as simply "new. scary. unnecessary." Introducing bindings mid-expression is a big expansion.

I also agree with @scottmcm that let..else would cover the vast majority of my use cases here. It also matches my preferred style- it makes it much easier to split up and name the parts of large conditions, rather than encouraging they all be combined into one giant if let.

@petrochenkov
Copy link
Contributor

@rpjohnst

I don't think the reaction can be fairly summarized as simply "new. scary. unnecessary."

This is just bad wording on my side, I didn't mean it's a summary for all reactions, just the most popular concern.
Concerns about in-expression bindings in general are not in this category.

@Centril
Copy link
Contributor Author

Centril commented Jan 8, 2018

Closing as promised.

@petrochenkov
Copy link
Contributor

petrochenkov commented Feb 20, 2018

So, I finally completed my prototype of non-exhaustive pattern-matching expressions, including in-expression bindings.
The implementation is available on my branch: https://github.com/petrochenkov/rust/tree/isproto.
I also ported rustfmt to use is instead of if let, while let and sometimes match.
Rustfmt branch is available here - https://github.com/petrochenkov/rustfmt/tree/isproto.

Some notes:

  • I used EXPR is PAT (or rather EXPR is PAT1 | PAT2 | ...) as syntax because I believe this is the right syntax in general, but also because it's legacy-free and I could freely tweak scopes and lifetimes of introduced bindings while successfully compiling existing code not using the feature.
  • The feature is implemented as a transformation on AST and resolution tables. Lowering to MIR may be a better place, but I'm just familiar with front-end better.
  • Desugaring heavily relies on labeled loops and labeled breaks with values (see comments in desugar_is.rs).
  • Limitations: is doesn't work in constant expressions due to limitations of const evaluator (should be fixed by Allow if and match in constants #2342 and Allow locals and destructuring in const fn #2341), is with bindings doesn't work in if guards in match due to lack of break 'arm construction (see propose if let guard #2294 (comment)).
  • Two variants of binding scopes are implemented - the longer one (containing block), by default, and the shorter one (current statement/full expression) enabled by a crate-level attribute.
  • The "longer" variant allows to refer to is bindings after the statement they were introduced in (similarly to plain let), but they are still considered potentially uninitialized.
    For them to be considered initialized we need to incorporate pattern exhaustiveness into initialization checking. Basically, this should work:
    let x;
    match Some(10) {
        Some(..) | None => {
            x = 11;
        }
        _ => {} // Unreachable
    }
    let y = x; // Currently ERROR: use of possibly uninitialized variable: `x`
  • The "longer" variant can result in surprising and undesired shadowing and overlong lifetimes compared to the "short" variant (see commit switching to the short variant and code "fixed" by it here - petrochenkov/rustfmt@f218480).
  • The "short" variant still can result in overlong lifetimes compared to existing if let/while let (without NLL), examples of such overlong lifetimes are marked with "ISPROTO" comments in my rustfmt branch.
    I tried to build rustfmt with feature(nll) to find out whether the borrowing errors disappear with it, but hit an ICE (1.25.0-nightly: internal compiler error: unresolved type in dtorck rust#48132).
    Update: the borrowing issue in minified form looks like get_temporary() is Pattern(ref binding) and it's indeed fixed by NLL!
  • Overlong lifetimes in while epxr is pat { ... } are especially funny because bindings always have to be mut - they live for the whole loop and are assigned in every loop cycle!
    Bindings introduced in while let notably live for a single cycle so they don't have to be mut. So, it may be reasonable to reduce scopes/lifetimes of is bindings to something even shorter than full expressions, but full expressions are a viable conservative variant.
  • I have found no examples of the struct literal parsing issue (RFC: if- and while-let-chains #2260 (comment)) in rustfmt.

@I60R
Copy link

I60R commented Jul 3, 2018

My suggestion about || scoping rules:

  1. Bindings introduced on both || sides lives in different scopes
    This means that bindings introduced on left || side are not visible on right.
    (x is Ok(y) || y.condition()) ⇒ error (unless y is not defined in parent scope)
  2. Binding always should be "mirrored" on other || side to be visible after condition
    This will provide guarantee that bindings introduced in all conditions can be always accessed safely after && operator and in if→then expression.
    Also in this way it will be easier to think about scopes and shadowing.
    (x is Ok(y) || condition()) ⇒ error, y must be defined on other || side
    (x is Ok(y) || z is Ok(y)) && y.condition()) ⇒ valid
  3. If binding is required only in one || side scope, it should be explicitly declared as "local"
    This will relax second rule when required (I'm sure, there will be use cases).
    Special syntax will determine scope where binding is visible:
    // suggestion 1
    if ('l: x is Ok('l y) && y.condition()) || condition() { 
    }
    // suggestion 2
    if x is Ok('l y) && y.condition() 'l || condition() {
    }
    // suggestion 3
    if x is Ok(priv y) && y.condition() || condition() {
    }
    // there might be other syntax as well

JohnTitor added a commit to JohnTitor/rust that referenced this pull request Jul 17, 2022
…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
workingjubilee pushed a commit to tcdi/postgrestd that referenced this pull request Sep 15, 2022
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.