Skip to content

eRFC: if- and while-let-chains, take 2 - #2497

Merged
Centril merged 15 commits into
rust-lang:masterfrom
Centril:rfc/let-chains-2
Aug 24, 2018
Merged

eRFC: if- and while-let-chains, take 2#2497
Centril merged 15 commits into
rust-lang:masterfrom
Centril:rfc/let-chains-2

Conversation

@Centril

@Centril Centril commented Jul 13, 2018

Copy link
Copy Markdown
Contributor

🖼️ Rendered

⏭ Tracking issue - Main

⏭ Tracking issue - Edition transitioning

📝 Summary

Extends if let and while let-expressions with chaining, allowing you to combine multiple lets and bool-typed conditions together naturally. After implementing this RFC, you'll be able to write, among other things:

fn param_env<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, def_id: DefId) -> ParamEnv<'tcx> {
    if let Some(Def::Existential(_)) = tcx.describe_def(def_id)
        && let Some(node_id) = tcx.hir.as_local_node_id(def_id)
        && let hir::map::NodeItem(item) = tcx.hir.get(node_id)
        && let hir::ItemExistential(ref exist_ty) = item.node
        && let Some(parent) = exist_ty.impl_trait_fn
    {
        return param_env(tcx, parent);
    }

    ...
}

The main aim of this RFC is to decide that this is a problem worth solving as well as discussing a few available options. Most importantly, we want to make let PAT = EXPR && .. a possible option for Rust 2018.

💖 Thanks

To everyone who participated in RFC #2260 and to the survey participants.
To @scottmcm for collaborating with me on the original RFC.
To @aturon and @nikomatsakis for taking the time to discuss this with me.
To @SergioBenitez for checking the feasibility of this in Rust 2018.
To @oli-obk for providing me with the useful example in the summary ;)
To @joshtriplett for the consultation on the style.
To @kennytm for improving clarity on the Rust 2018 migration technical changes.

@Centril Centril added T-lang Relevant to the language team, which will review and decide on the RFC. I-nominated labels Jul 13, 2018
Comment thread text/0000-if-let-chains.md Outdated
@SergioBenitez

ghost commented Jul 13, 2018

Copy link
Copy Markdown
Contributor

I am very for this change. This syntax feels natural and intuitive, and it is something I've pined for.

My remarks are:

  1. While I have often wanted to reach for if let chains, I've never instinctively wanted while let chains. As such, personally, I do not see while let chaining as a necessary addition.
  2. With somewhat less frequency than && chaining, I have also reached for || chaining in if let clauses. In particular, in combination with &&:
    if let A(x) = foo() || (let B(x) == bar() && x.is_this()) {
        // use x
    }
    Again, this construction and syntax feel natural to me, and I would consider it if let chaining without || to be inconsistent with the rest of the language. That being said, I'd rather have && chaining alone than nothing at all.

@Centril

ghost commented Jul 13, 2018

Copy link
Copy Markdown
Contributor Author

@SergioBenitez

I've never instinctively wanted while let chains. As such, personally, I do not see while let chaining as a necessary addition.

Thank you for the data point and the remarks.

I think the primary motivation for also changing while let is consistency with if let so as to reduce surprises for users. Uniformity is our friend in making teaching and learning easier :)

That said, if we consider iterating over multiple sources that produce elements in a zip-like fashion (but not strictly .zip(..) for iterators), then I think chaining while let can be useful. It can also be useful to chain while let with a side-condition as in while let Foo(bar) && bar.is_special() { .. }.

Again, this construction and syntax feel natural to me, and I would consider it if let chaining without || to be inconsistent with the rest of the language. That being said, I'd rather have && chaining alone than nothing at all.

I'm personally undecided on adding || here. What this RFC does do is to make || a possible addition in Rust 2018, if we so choose. For now, due to the time constraints of shipping the edition, I'd like to punt on adding || to the language to a future RFC and discussions.

Comment thread text/0000-if-let-chains.md
Comment thread text/0000-if-let-chains.md Outdated
@Centril

ghost commented Jul 13, 2018

Copy link
Copy Markdown
Contributor Author

@aturon has asked me to clarify what exactly is being proposed to be accepted with this RFC. So here goes...

The precedent set by closing RFCs #2443, #2441, and #2429 is that we don't accept RFCs reserving syntax without accepting a feature along with it. Therefore, to be consistent with this policy, and for the purposes of making if let PAT = EXPR && ... a possible syntax in Rust 2018, we need also to accept a feature along with the reservation of this syntax in Rust 2018.

So the idea with this RFC is that we accept the full feature now (the idea of chaining...), but leave finalizing the syntax itself unresolved-ish as we did with #2071 (existential type Foo: Bar; - unlike 2071, this RFC does not suggest a temporary syntax however). This will leave us room both to make the changes necessary in terms of the lint in Rust 2015 as well as the hard error in Rust 2018.

After the Rust 2018 has shipped, and when we have time, we can then implement the proposed syntax and experiment with it on nightly. The syntax will still have to be finalized by some other decision, such as with another RFC or on the tracking issue.

Comment thread text/0000-if-let-chains.md Outdated
Comment thread text/0000-if-let-chains.md Outdated
Comment thread text/0000-if-let-chains.md
Comment thread text/0000-if-let-chains.md
Comment thread text/0000-if-let-chains.md
Comment thread text/0000-if-let-chains.md
@petrochenkov

ghost commented Jul 13, 2018

Copy link
Copy Markdown
Contributor

My opinion hasn't changed since #2260 (comment).
I still think this is a half-assed solution that will likely block a proper solution in the future (for social rather than technical reasons).

We should aim for supporting a convenient non-exhaustive pattern-matching expressions in general (EXPR op PAT: bool) rather than special case if and while, because it solves much more issues than chaining alone.

Furthermore, as evidenced in RFC 2260, making EXPR is PAT, which has other problems we've previously noted, an expression is also tricky due to the non-obvious scoping rules for bindings it entails. Mainly because of this, support for EXPR is PAT has been slow to develop.

That's just not true, for expressions in if and while the rules can be exactly the same as in this RFC, and outside of if/while these expressions can desugar into if expr { true } else { false } (at least this is how it's done in my implementation).

If "slow to develop" refers to my promise to write an RFC for pattern-matching expressions
based on my implementation (#2411 (comment)), then the reason is that I was busy with more urgent things and deferred this work until 2018 edition completion.
Sigh, logistically this RFC is perfectly timed, since I still can't write a counter-RFC right now or spend too much time arguing.

@aturon

ghost commented Jul 13, 2018

Copy link
Copy Markdown
Contributor

@petrochenkov Totally understood -- did you see @Centril's clarifying comment? My understanding is that this RFC is meant to commit us to (1) some solution to this problem and (2) carving out a bit of space in the grammar so that the solution could be as discussed in the RFC.

FWIW, I also have significant misgivings about solving this problem by essentially extending the special-cased treatment of if let, and would prefer something more compositional. But I am in favor of the commitments being proposed.

@Centril I wonder if this should be made formally an "eRFC" to signify more clearly the issues above?

@petrochenkov

ghost commented Jul 13, 2018

Copy link
Copy Markdown
Contributor

@aturon
Oh, if this is actually about reserving if let PAT = EXPR1 && EXPR2 as if (let PAT = EXPR1) && EXPR2 in the grammar, then I'm on board 😄
I'm just worrying that the accepted version will become set in stone eventually.

@kennytm

ghost commented Jul 13, 2018

Copy link
Copy Markdown
Member

These operators all have lower precedence than &&:

  • ||
  • .., ..=
  • =, +=, etc
  • return, break

Which means we also have the following ambiguity to consider:

    if let Range { start: _, end: false } = true..true && false {
        println!("(current behavior)");
    } else {
        panic!();
    }
    let result = loop {
        if let Some(0u32) = break true && false {
            panic!();
        }
        panic!();
    };
    assert_eq!(result, false);

And then || and && can be interpreted prefix operators:

    const F: fn() -> bool = || true;
    if let Range { start: F, end } = F..|| false {
        println!("(current behavior)");
        assert!(!end());
    } else {
        panic!();
    }
    let t = &&true;
    if let Range { start: true, end } = t..&&false {
        println!("(current behavior)");
        assert_eq!(**end, false);
    } else {
        panic!();
    }

Please also ensure the parser accepts the following:

if a || b {}   // <-- no parenthesis, it is ok.
if a && b || c && d {}
// interpret as: `(a && b) || (c && d)`

and rejects the following before if-let-or is supported:

if let Some(a) = b && c || d {}
// interpret as: `((let Some(a) = b) && c) || d`

@Centril Centril changed the title RFC: if- and while-let-chains, take 2 eRFC: if- and while-let-chains, take 2 Jul 13, 2018
@Centril

ghost commented Jul 13, 2018

Copy link
Copy Markdown
Contributor Author

@aturon eRFC it is ;)

@petrochenkov Yep, it's about making the proposed solution possible and recognizing that some solution (which could be EXPR match PAT) is necessary.

I would personally be fine with experimenting with if EXPR match PAT && .. and if let PAT = EXPR && .. concurrently in the nightly compiler and see which one comes out on top.

That's just not true, for expressions in if and while the rules can be exactly the same as in this RFC, and outside of if/while these expressions can desugar into if expr { true } else { false } (at least this is how it's done in my implementation).

I see; my impression was that it was more complicated from the discussions at #2260. This is the "bindings only live until the end of a full expression" rule?

If "slow to develop" refers to my promise to write an RFC for pattern-matching expressions
based on my implementation (#2411 (comment)),

Not at all :) It is referring to my perception of the support for EXPR op PAT within the community and the lang team (which could be wrong, but the survey is at least quite suggestive as to the community support).

then the reason is that I was busy with more urgent things and deferred this work until 2018 edition completion.

I thank you for it ❤️

@joshtriplett

ghost commented Jul 13, 2018

Copy link
Copy Markdown
Member

I can't say I'm a huge fan of the specific proposed syntax, but I'm inclined to take this step regardless, so that even if we don't end up going with this particular syntax we can detect potentially ambiguous uses of it. I'm honestly tempted to argue that either grouping should require parentheses.

Minor nit: for consistency, can you please always put && at the start of a line, rather than at the end? In addition to matching the standard style/rustfmt, I also think that more clearly distinguishes the && from the expression on the right-hand side of the let.

Also, in the example of how while let would translate to a loop, you have unbalanced {s at the ends of several lines.

Comment thread text/0000-if-let-chains.md Outdated
@Centril Centril self-assigned this Jul 13, 2018
@hadronized

ghost commented Jan 31, 2019

Copy link
Copy Markdown
Contributor

In the code you showed, you could have used a guarded match. That’s already part of the language and it will do the job for most cases. Having lazy pattern-matching mixed with booleans makes me incomfortable. I’m not saying I’m against lazy pattern-matching (it’s actually handy). I’m just saying that using the if keyword for that is not a good move. But as I said yesterday, the RFC is already merged and we’re just bikeshedding. Tant pis pour moi

@kennytm kennytm mentioned this pull request Aug 23, 2021
@alper

ghost commented Dec 6, 2024

Copy link
Copy Markdown

Can somebody explain to me why the Swift if let with commas was not considered?

@kennytm

ghost commented Dec 6, 2024

Copy link
Copy Markdown
Member

@alper It was considered and the community highly disliked it. There is literally a survey report inside the RFC document.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-control-flow Proposals relating to control flow. A-syntax Syntax related proposals & ideas disposition-merge This RFC is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this RFC. 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.