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 for while let statement #214

Merged
merged 3 commits into from
Oct 1, 2014
Merged

RFC for while let statement #214

merged 3 commits into from
Oct 1, 2014

Conversation

lilyball
Copy link
Contributor

  • Start Date: 2014-08-27
  • RFC PR: (leave this empty)
  • Rust Issue: (leave this empty)

Summary

Introduce a new while let PAT = EXPR { BODY } construct. This allows for using a refutable pattern
match (with optional variable binding) as the condition of a loop.

Motivation

Just as if let was inspired by Swift, it turns out Swift supports while let as well. This was
not discovered until much too late to include it in the if let RFC. It turns out that this sort of
looping is actually useful on occasion. For example, the desugaring for loop is actually a variant
on this; if while let existed it could have been implemented to map for PAT in EXPR { BODY } to

// the match here is so `for` can accept an rvalue for the iterator,
// and was used in the "real" desugaring version.
match &mut BODY {
    i => {
        while let Some(PAT) = i.next() {
            BODY
        }
    }
}

(note that the non-desugared form of for is no longer equivalent).

More generally, this construct can be used any time looping + pattern-matching is desired.

This also makes the language a bit more consistent; right now, any condition that can be used with
if can be used with while. The new if let adds a form of if that doesn't map to while.
Supporting while let restores the equivalence of these two control-flow constructs.

Detailed design

while let operates similarly to if let, in that it desugars to existing syntax. Specifically,
the syntax

['ident:] while let PAT = EXPR {
    BODY
}

desugars to

['ident:] loop {
    match EXPR {
        PAT => BODY,
        _ => break
    }
}

Just as with if let, an irrefutable pattern given to while let is considered an error. This is
largely an artifact of the fact that the desugared match ends up with an unreachable pattern,
and is not actually a goal of this syntax. The error may be suppressed in the future, which would be
a backwards-compatible change.

Just as with if let, while let will be introduced under a feature gate (named while_let).

Drawbacks

Yet another addition to the grammar. Unlike if let, it's not obvious how useful this syntax will
be.

Alternatives

As with if let, this could plausibly be done with a macro, but it would be ugly and produce bad
error messages.

while let could be extended to support alternative patterns, just as match arms do. This is not
part of the main proposal for the same reason it was left out of if let, which is that a) it looks
weird, and b) it's a bit of an odd coupling with the let keyword as alternatives like this aren't
going to be introducing variable bindings. However, it would make while let more general and able
to replace more instances of loop { match { ... } } than is possible with the main design.

Unresolved questions

None.

@lilyball
Copy link
Contributor Author

Personally, the biggest motivating reason to do this is consistency (last paragraph of the motivation). Though @cmr did say he's wanted a construct like this in the past.

@Valloric
Copy link

While I'm not personally against this and I love if let, I think while let may not be worth the extra language complexity. It doesn't seem to address a pressing use-case, while if let really does. If we're adding this simply because it feels symmetrical with if let, IMO it's probably not worth it.

If we're adding this because we want to align more with Swift and make the Swift -> Rust transition easier for users (let's keep in mind that Swift will be a major language, by Apple fiat), that might be a good enough reason.

@lilyball
Copy link
Contributor Author

Yeah, I didn't state it outright in the document, but as long as we're borrowing if let from Swift, borrowing while let makes sense. It makes the transition just a little bit easier; otherwise, it may be a point of confusion.

@lilyball
Copy link
Contributor Author

FWIW I just did a quick search for loop { match in the rust source, and picking a few arbitrary results, I've found some locations where while let could be used (note: 9-day-old version of master):

libsyntax/print/pprust.rs:2564:

loop {
    match self.next_comment() {
        Some(ref cmnt) => {
            // ...
        }
        _ => break
    }
}

This would become

while let Some(ref cmnt) = self.next_comment() {
    // ...
}

Same thing again at line 2654 in the same file.


libsyntax/parse/mod.rs:412:

loop {
    match chars.next() {
        Some((i, c)) => { /* ... */ }
        None = break
    }
}

would become

while let Some((i, c)) = chars.next() {
    // ...
}

Same pattern again at line 462 in the same file.


libsynta/ext/format.rs:731:

loop {
    match parser.next() {
        Some(piece) => { /* ... */ }
        None => break
    }
}

would become

while let Some(piece) = parser.next() {
    // ...
}

And so on. You get the idea. There are more spots that could be updated if we supported alternatives in while let, e.g. in libsyntax/parse/mod.rs:558:

while let Some(b' ') | Some(b'\n') | Some(b'\r') | Some(b'\t') = it.peek().map(|x| x.val1()) {
    it.next();
}

However, I didn't propose alternatives here, because it looks kind of weird. Same reason I didn't put them in if let. Still, it's worth considering.

@lilyball
Copy link
Contributor Author

RFC alternatives section updated for pattern alternatives.

@lilyball lilyball closed this Aug 28, 2014
@lilyball lilyball reopened this Aug 28, 2014
@lilyball
Copy link
Contributor Author

Oops, wrong button. Thanks for making that so easy, GitHub.

@arcto
Copy link

arcto commented Aug 28, 2014

What I like about this is that I think it would encourage a prevalent use of Option by reducing cognitive and syntactical costs, and also allow many algorithms to be read more fluently.

while let Some(piece) = parser.next() {
    // ...
}

vs

'a: loop {
    if let Some(piece) = parser.next() {
        // ...
    } else {
        break 'a;
    }
}

How viable is the Iterator-map approach for widespread common-case use?

@reem
Copy link

reem commented Aug 28, 2014

@arcto Well, we'd rather people just use for piece in parser {}, so I'm not sure that's a compelling use case.

@arcto
Copy link

arcto commented Aug 28, 2014

@reem Good point.
So while the proposed syntax is short, pleasant and consistent with if let it's really just a matter of weighing the effect on the language complexity itself. I've not much to add to that discussion.

Perhaps data on the prevalence of applicable places in current codebases could be used as basis for a decision.

Even though Swift is rather new are there any indicators on the feature's reception over there?

@ftxqxd
Copy link
Contributor

ftxqxd commented Aug 28, 2014

@kballard Your last two examples (from libsyntax) were just workarounds for a bug in the old for desugaring. That bug has now been fixed, so it should be rewritten as a for loop. (There’s even a FIXME in the source.)

That said, I like the RFC (mainly for consistency with if ... let), so I found a few more examples:

libsyntax/ext/tt/macro_parser.rs:256

loop {
    let ei = match cur_eis.pop() {
        None => break, /* for each Earley Item */
        Some(ei) => ei,
    }
    ...
};

could be rewritten as

while let Some(ei) = cur_eis.pop() {
    ...
}

A lot of these follow a pattern of while let Some(foo) = bar.pop() { ... }, which is interesting. while ... let certainly makes that nicer.

@glaebhoerl
Copy link
Contributor

I'd like to have this. It follows logically from if let, doesn't have any apparent drawbacks, and would help make peoples' lives easier.

I think disjunctive patterns should eventually be extended to let, if let, and while let at the same time by making them part of pattern syntax in general, as per RFC PR 99.

```rust
// the match here is so `for` can accept an rvalue for the iterator,
// and was used in the "real" desugaring version.
match &mut BODY {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be match &mut EXPR {.

@treeman
Copy link

treeman commented Aug 29, 2014

I think the point @arcto makes about makes about Option and ease of use is a good reason for while let. Generally encouraging an increased use of Option is worthwhile.

If we disallow while let but allow if let then we introduce special cases into the language which may trip up beginners and actually make the language more complex to learn. This alone is a strong reason accept.

Big 👍

@mbrubeck
Copy link
Contributor

+1. Along with the consistency argument (which I think is a good one), we have several places in Servo that would benefit from this, like this code that calls recv in a loop:

        loop {
            match self.from_client.recv() {
              Load(load_data, start_chan) => {
                self.load(load_data, start_chan)
              }
              Exit => {
                break
              }
            }
        }

which could be rewritten as:

        while let Load(load_data, start_chan) = self.from_client.recv() {
            self.load(load_data, start_chan)
        }

@blaenk
Copy link
Contributor

blaenk commented Sep 22, 2014

I'm for this, even just for the consistency argument but also because I think it does make some code much more readable.

@nodakai
Copy link

nodakai commented Sep 24, 2014

FYI: C++ allows a simple variable definition in the condition part of if, switch, while and for (we can also define variables in the initialization part of for.) The variable can be of any type that can be converted to bool (including a pointer.) But I've used such a variable definition only with if so far.

#include <iostream>
using namespace std;

int main() {
    if (int x = 10 % 2) {
        cout << "10 % 2 == " << x << endl;
    }
    if (int x = 10 % 3) {
        cout << "10 % 3 == " << x << endl;
    }

    switch (int x = 10 % 2) {
        case 0: cout << "zero: " << x << endl; break;
        case 1: cout << "one: " << x << endl; break;
    }
    switch (int x = 10 % 3) {
        case 0: cout << "zero: " << x << endl; break;
        case 1: cout << "one: " << x << endl; break;
    }

    int i = 9;
    while (int r = ++i % 3) {
        cout << i << ": " << r << endl;
    }

    for (int j = 1, k = 1; int diff = j*j - 3*k; j++,k++) {
        cout << "j: " << j << ", k: " << k << ", diff: " << diff << endl;
    }
    return 0;
}

On the other hand, the D programming language doesn't allow such a definition in the condition part of any of while, switch and for.

@aturon aturon merged commit c1dd38e into rust-lang:master Oct 1, 2014
@aturon
Copy link
Member

aturon commented Oct 1, 2014

This RFC has been accepted.

Tracking issue
Meeting minutes

@burdges burdges mentioned this pull request Jun 1, 2017
@Centril Centril added A-syntax Syntax related proposals & ideas A-expressions Term language related proposals & ideas A-control-flow Proposals relating to control flow. A-exhaustiveness Proposals relating to exhaustiveness of pattern matching. labels Nov 23, 2018
wycats pushed a commit to wycats/rust-rfcs that referenced this pull request Mar 5, 2019
* Cleanup

* More
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-exhaustiveness Proposals relating to exhaustiveness of pattern matching. A-expressions Term language related proposals & ideas A-syntax Syntax related proposals & ideas
Projects
None yet
Development

Successfully merging this pull request may close these issues.