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 `if let` expression #160

Merged
merged 2 commits into from Aug 27, 2014

Conversation

Projects
None yet
@kballard
Contributor

kballard commented Jul 9, 2014

  • Start Date: (fill me in with today's date, YYYY-MM-DD)
  • RFC PR #: (leave this empty)
  • Rust Issue #: (leave this empty)

Summary

Introduce a new if let PAT = EXPR { BODY } construct. This allows for refutable pattern matching
without the syntactic and semantic overhead of a full match, and without the corresponding extra
rightward drift. Informally this is known as an "if-let statement".

Motivation

Many times in the past, people have proposed various mechanisms for doing a refutable let-binding.
None of them went anywhere, largely because the syntax wasn't great, or because the suggestion
introduced runtime failure if the pattern match failed.

This proposal ties the refutable pattern match to the pre-existing conditional construct (i.e. if
statement), which provides a clear and intuitive explanation for why refutable patterns are allowed
here (as opposed to a let statement which disallows them) and how to behave if the pattern doesn't
match.

The motivation for having any construct at all for this is to simplify the cases that today call for
a match statement with a single non-trivial case. This is predominately used for unwrapping
Option<T> values, but can be used elsewhere.

The idiomatic solution today for testing and unwrapping an Option<T> looks like

match optVal {
    Some(x) => {
        doSomethingWith(x);
    }
    None => {}
}

This is unnecessarily verbose, with the None => {} (or _ => {}) case being required, and
introduces unnecessary rightward drift (this introduces two levels of indentation where a normal
conditional would introduce one).

The alternative approach looks like this:

if optVal.is_some() {
    let x = optVal.unwrap();
    doSomethingWith(x);
}

This is generally considered to be a less idiomatic solution than the match. It has the benefit of
fixing rightward drift, but it ends up testing the value twice (which should be optimized away, but
semantically speaking still happens), with the second test being a method that potentially
introduces failure. From context, the failure won't happen, but it still imposes a semantic burden
on the reader. Finally, it requires having a pre-existing let-binding for the optional value; if the
value is a temporary, then a new let-binding in the parent scope is required in order to be able to
test and unwrap in two separate expressions.

The if let construct solves all of these problems, and looks like this:

if let Some(x) = optVal {
    doSomethingWith(x);
}

Detailed design

The if let construct is based on the precedent set by Swift, which introduced its own if let
statement. In Swift, if let var = expr { ... } is directly tied to the notion of optional values,
and unwraps the optional value that expr evaluates to. In this proposal, the equivalent is if let Some(var) = expr { ... }.

Given the following rough grammar for an if condition:

if-expr     = 'if' if-cond block else-clause?
if-cond     = expression
else-clause = 'else' block | 'else' if-expr

The grammar is modified to add the following productions:

if-cond = 'let' pattern '=' expression

The expression is restricted to disallow a trailing braced block (e.g. for struct literals) the
same way the expression in the normal if statement is, to avoid ambiguity with the then-block.

Contrary to a let statement, the pattern in the if let expression allows refutable patterns. The
compiler should emit a warning for an if let expression with an irrefutable pattern, with the
suggestion that this should be turned into a regular let statement.

Like the for loop before it, this construct can be transformed in a syntax-lowering pass into the
equivalent match statement. The expression is given to match and the pattern becomes a match
arm. If there is an else block, that becomes the body of the _ => {} arm, otherwise _ => {} is
provided.

Optionally, one or more else if (not else if let) blocks can be placed in the same match using
pattern guards on _. This could be done to simplify the code when pretty-printing the expansion
result. Otherwise, this is an unnecessary transformation.

Due to some uncertainty regarding potentially-surprising fallout of AST rewrites, and some worries
about exhaustiveness-checking (e.g. a tautological if let would be an error, which may be
unexpected), this is put behind a feature gate named if_let.

Examples

Source:

if let Some(x) = foo() {
    doSomethingWith(x)
}

Result:

match foo() {
    Some(x) => {
        doSomethingWith(x)
    }
    _ => {}
}

Source:

if let Some(x) = foo() {
    doSomethingWith(x)
} else {
    defaultBehavior()
}

Result:

match foo() {
    Some(x) => {
        doSomethingWith(x)
    }
    _ => {
        defaultBehavior()
    }
}

Source:

if cond() {
    doSomething()
} else if let Some(x) = foo() {
    doSomethingWith(x)
} else {
    defaultBehavior()
}

Result:

if cond() {
    doSomething()
} else {
    match foo() {
        Some(x) => {
            doSomethingWith(x)
        }
        _ => {
            defaultBehavior()
        }
    }
}

With the optional addition specified above:

if let Some(x) = foo() {
    doSomethingWith(x)
} else if cond() {
    doSomething()
} else if other_cond() {
    doSomethingElse()
}

Result:

match foo() {
    Some(x) => {
        doSomethingWith(x)
    }
    _ if cond() => {
        doSomething()
    }
    _ if other_cond() => {
        doSomethingElse()
    }
    _ => {}
}

Drawbacks

It's one more addition to the grammar.

Alternatives

This could plausibly be done with a macro, but the invoking syntax would be pretty terrible and
would largely negate the whole point of having this sugar.

Alternatively, this could not be done at all. We've been getting alone just fine without it so far,
but at the cost of making Option just a bit more annoying to work with.

Unresolved questions

It's been suggested that alternates or pattern guards should be allowed. I think if you need those
you could just go ahead and use a match, and that if let could be extended to support those in
the future if a compelling use-case is found.

I don't know how many match statements in our current code base could be replaced with this
syntax. Probably quite a few, but it would be informative to have real data on this.

@liigo

This comment has been minimized.

Show comment
Hide comment
@liigo

liigo Jul 9, 2014

Contributor

I like this!

Contributor

liigo commented Jul 9, 2014

I like this!

@chris-morgan

This comment has been minimized.

Show comment
Hide comment
@chris-morgan

chris-morgan Jul 9, 2014

Member

This has interesting potential. Where I might currently write

match foo {
    A { .. } => {
        a
    }
    B(..) => {
        b
    }
    C => {
        c
    }
    _ => {
        d
    }
}

I could now write

if let A { .. } = foo {
    a
} else if let B(..) = foo {
    b
} else if let C = foo {
    c
} else {
    d
}

Exhaustiveness checking wouldn’t come with this way of doing it, though. (Leastways, not easily, and making it an error would be distinctly suspect.)

I think you’re probably right about guards. Although it could potentially be nice to have them, if let PAT if GUARD = EXPR is just a bit too icky.

👍 from me.

Member

chris-morgan commented Jul 9, 2014

This has interesting potential. Where I might currently write

match foo {
    A { .. } => {
        a
    }
    B(..) => {
        b
    }
    C => {
        c
    }
    _ => {
        d
    }
}

I could now write

if let A { .. } = foo {
    a
} else if let B(..) = foo {
    b
} else if let C = foo {
    c
} else {
    d
}

Exhaustiveness checking wouldn’t come with this way of doing it, though. (Leastways, not easily, and making it an error would be distinctly suspect.)

I think you’re probably right about guards. Although it could potentially be nice to have them, if let PAT if GUARD = EXPR is just a bit too icky.

👍 from me.

@sinistersnare

This comment has been minimized.

Show comment
Hide comment
@sinistersnare

sinistersnare Jul 9, 2014

I don't know if I like this, it adds unneeded complexity to the language, and is just sugar for a time in this language where we do not need/want sugar (of course this could be a post 1.0 thing then disregard that part). Also it is not the easiest thing to grok, it confused me for a good bit. I also dislike the syntax, but if it gets accepted I do not think I will complain very much.

-1

sinistersnare commented Jul 9, 2014

I don't know if I like this, it adds unneeded complexity to the language, and is just sugar for a time in this language where we do not need/want sugar (of course this could be a post 1.0 thing then disregard that part). Also it is not the easiest thing to grok, it confused me for a good bit. I also dislike the syntax, but if it gets accepted I do not think I will complain very much.

-1

@nielsle

This comment has been minimized.

Show comment
Hide comment
@nielsle

nielsle Jul 9, 2014

Just a nitpick: If optVal is an option, then you can also do

optVal.map( |x| 
    do_something_with(*x)
);

nielsle commented Jul 9, 2014

Just a nitpick: If optVal is an option, then you can also do

optVal.map( |x| 
    do_something_with(*x)
);
@kballard

This comment has been minimized.

Show comment
Hide comment
@kballard

kballard Jul 9, 2014

Contributor

@nielsle Only if using a closure isn't a problem, which it often can be, and if you have no need for an else clause.

Contributor

kballard commented Jul 9, 2014

@nielsle Only if using a closure isn't a problem, which it often can be, and if you have no need for an else clause.

@kballard

This comment has been minimized.

Show comment
Hide comment
@kballard

kballard Jul 9, 2014

Contributor

@sinistersnare What do you mean, a time in this language where we do not need/want sugar? for loops are sugar. Do you think those aren't helpful?

Contributor

kballard commented Jul 9, 2014

@sinistersnare What do you mean, a time in this language where we do not need/want sugar? for loops are sugar. Do you think those aren't helpful?

@sinistersnare

This comment has been minimized.

Show comment
Hide comment
@sinistersnare

sinistersnare Jul 9, 2014

The way I see it, we are striping the language to its bare essentials and necessary features for 1.0, and working out ergonomics from there.

As you said, this could be easily desugared into a match block, whereas a for loop is a classic idiom from many programming languages. I consider the way for loops are done in Rust a huge selling point for the language, safer, faster, and better looking.

I just do not think this would be greatly beneficial to Rust, but if the majority opinion says go, sure why not.

sinistersnare commented Jul 9, 2014

The way I see it, we are striping the language to its bare essentials and necessary features for 1.0, and working out ergonomics from there.

As you said, this could be easily desugared into a match block, whereas a for loop is a classic idiom from many programming languages. I consider the way for loops are done in Rust a huge selling point for the language, safer, faster, and better looking.

I just do not think this would be greatly beneficial to Rust, but if the majority opinion says go, sure why not.

@bachm

This comment has been minimized.

Show comment
Hide comment
@bachm

bachm Jul 9, 2014

To me it seems the benefit of this is essentially not having to write the None => {} case in a match. In other words, we want a non-exhaustive version of match, which seems like the simpler solution. Let's call it select:

select optVal {
    Some(x) => doSomethingWith(x)
}

bachm commented Jul 9, 2014

To me it seems the benefit of this is essentially not having to write the None => {} case in a match. In other words, we want a non-exhaustive version of match, which seems like the simpler solution. Let's call it select:

select optVal {
    Some(x) => doSomethingWith(x)
}
@netvl

This comment has been minimized.

Show comment
Hide comment
@netvl

netvl Jul 9, 2014

@bachm, I think the main benefit is that there is no extra nesting and indentation here. Your select will still need additional level of indentation.

The proposal looks great, +1 from me.

netvl commented Jul 9, 2014

@bachm, I think the main benefit is that there is no extra nesting and indentation here. Your select will still need additional level of indentation.

The proposal looks great, +1 from me.

@stepancheg

This comment has been minimized.

Show comment
Hide comment
@stepancheg

stepancheg Jul 9, 2014

I always wanted something like this.

BTW, I'd like to propose an alternative: is-expression. It looks like this:

if opt is Some(ref v) { ... }

is-expression in contrast to if-let:

  • looks more natural
  • allows mix of any expressions in if-cond, like:
if (has_next_in_buf() || fetch_from_stream()) && next() is Identifier(name) { ... }

is-expression can be used anywhere deep inside of any expression, like this:

foo(bar is [20, ..]) // pass true if bar is a slice starting with 20, otherwise pass false

However, pattern element can be bound to variable only if is-expression is in &&-argument of if-cond, thus this is invalid (or probably a warning):

if foo is Some(x) || bar is None { ... } // x is not in the scope of then-block

Somewhat similar is-expression is present in Kotlin programming language. Their is expression does two things:

  • checks instance type
  • does smart cast: inside of then-expression type of left argument of is is adjusted

stepancheg commented Jul 9, 2014

I always wanted something like this.

BTW, I'd like to propose an alternative: is-expression. It looks like this:

if opt is Some(ref v) { ... }

is-expression in contrast to if-let:

  • looks more natural
  • allows mix of any expressions in if-cond, like:
if (has_next_in_buf() || fetch_from_stream()) && next() is Identifier(name) { ... }

is-expression can be used anywhere deep inside of any expression, like this:

foo(bar is [20, ..]) // pass true if bar is a slice starting with 20, otherwise pass false

However, pattern element can be bound to variable only if is-expression is in &&-argument of if-cond, thus this is invalid (or probably a warning):

if foo is Some(x) || bar is None { ... } // x is not in the scope of then-block

Somewhat similar is-expression is present in Kotlin programming language. Their is expression does two things:

  • checks instance type
  • does smart cast: inside of then-expression type of left argument of is is adjusted
@dobkeratops

This comment has been minimized.

Show comment
Hide comment
@dobkeratops

dobkeratops Jul 9, 2014

alternatives - could macros be beefed up to reduce rightward drift. Imagine if you could do this...

expr.macro!(....) // macro with receiver, comes in as $self  .. 
macro! (..) { ... }   // macro with multiple bracket types, separate's its arguments more

opt.if_is!(Some(x)) { .. do stuff with x.. }

you could build macro forms that fit in more naturally, and have more ways of fighting rightward drift.
(here the motivation for a 'receiver' is approximating infix, not dispatch)

Other than that... to me it does seem sensible to borrow ideas from swift - the language will be very widespread. And "if let ...." certainly makes sense to me coming from C++ where we can write if (auto p=dynamic_cast<Foo*>(expr)) { ... do stuff with p.. }.

dobkeratops commented Jul 9, 2014

alternatives - could macros be beefed up to reduce rightward drift. Imagine if you could do this...

expr.macro!(....) // macro with receiver, comes in as $self  .. 
macro! (..) { ... }   // macro with multiple bracket types, separate's its arguments more

opt.if_is!(Some(x)) { .. do stuff with x.. }

you could build macro forms that fit in more naturally, and have more ways of fighting rightward drift.
(here the motivation for a 'receiver' is approximating infix, not dispatch)

Other than that... to me it does seem sensible to borrow ideas from swift - the language will be very widespread. And "if let ...." certainly makes sense to me coming from C++ where we can write if (auto p=dynamic_cast<Foo*>(expr)) { ... do stuff with p.. }.

@glaebhoerl

This comment has been minimized.

Show comment
Hide comment
@glaebhoerl

glaebhoerl Jul 9, 2014

Contributor

+1 to the original proposal just as it is. I love it when someone submits the same idea I was going to.

Contributor

glaebhoerl commented Jul 9, 2014

+1 to the original proposal just as it is. I love it when someone submits the same idea I was going to.

@kballard

This comment has been minimized.

Show comment
Hide comment
@kballard

kballard Jul 9, 2014

Contributor

@stepancheg An is operator like your proposing seems appropriate for pattern-matching, but it doesn't feel to me like it's appropriate for producing a let-binding as a result of destructuring. Especially if it's part of a && chain. For matching alone, your is operator looks basically like the proposed matches!() from rust-lang/rust#14685.

Contributor

kballard commented Jul 9, 2014

@stepancheg An is operator like your proposing seems appropriate for pattern-matching, but it doesn't feel to me like it's appropriate for producing a let-binding as a result of destructuring. Especially if it's part of a && chain. For matching alone, your is operator looks basically like the proposed matches!() from rust-lang/rust#14685.

@stepancheg

This comment has been minimized.

Show comment
Hide comment
@stepancheg

stepancheg Jul 9, 2014

@kballard no, is looks nothing like matches!(). matches!() doesn't bind variables, unlike is:

if foo is Some(ref s) && bar is Some(ref t) {
    println!("both foo and bar are some: {}, {}", s, t);
}

stepancheg commented Jul 9, 2014

@kballard no, is looks nothing like matches!(). matches!() doesn't bind variables, unlike is:

if foo is Some(ref s) && bar is Some(ref t) {
    println!("both foo and bar are some: {}, {}", s, t);
}
@kballard

This comment has been minimized.

Show comment
Hide comment
@kballard

kballard Jul 9, 2014

Contributor

@stepancheg Right, that's why I said "for matching alone". As I stated, I don't think it's appropriate to bind variables with an is statement, that feels extremely surprising and counterintuitive.

Contributor

kballard commented Jul 9, 2014

@stepancheg Right, that's why I said "for matching alone". As I stated, I don't think it's appropriate to bind variables with an is statement, that feels extremely surprising and counterintuitive.

@zwarich

This comment has been minimized.

Show comment
Hide comment
@zwarich

zwarich Jul 9, 2014

Contributor

@stepancheg Would you really want to allow multiple is instances in the same if? Then you could do things like

if foo is A(ref s) || bar is B(ref t) {
...
}

You have to bind every variable exactly once on every path through the conditions. The if let syntax makes this more clear and integrates the existing linearity checking for patterns.

Contributor

zwarich commented Jul 9, 2014

@stepancheg Would you really want to allow multiple is instances in the same if? Then you could do things like

if foo is A(ref s) || bar is B(ref t) {
...
}

You have to bind every variable exactly once on every path through the conditions. The if let syntax makes this more clear and integrates the existing linearity checking for patterns.

@stepancheg

This comment has been minimized.

Show comment
Hide comment
@stepancheg

stepancheg Jul 9, 2014

@zwarich Multiple is separated by || should be allowed similarly to how multiple alternatives are allowed in match: all alternatives must fill the same variable:

struct Foo(int, int);

fn bar(foo: Foo) {
    match foo {
        Foo(1, x) | Foo(y, 2) => {}, // error
        Foo(3, x) | Foo(x, 4) => {}, // OK
        _ => {},
    }
}

fn baz(foo1: Foo, foo2: Foo) {
    if foo1 is Foo(1, x) || foo2 is Foo(y, 2) { ... } // error
    if foo1 is Foo(3, x) || foo2 is Foo(x, 4) { ... } // OK
}

stepancheg commented Jul 9, 2014

@zwarich Multiple is separated by || should be allowed similarly to how multiple alternatives are allowed in match: all alternatives must fill the same variable:

struct Foo(int, int);

fn bar(foo: Foo) {
    match foo {
        Foo(1, x) | Foo(y, 2) => {}, // error
        Foo(3, x) | Foo(x, 4) => {}, // OK
        _ => {},
    }
}

fn baz(foo1: Foo, foo2: Foo) {
    if foo1 is Foo(1, x) || foo2 is Foo(y, 2) { ... } // error
    if foo1 is Foo(3, x) || foo2 is Foo(x, 4) { ... } // OK
}
@zwarich

This comment has been minimized.

Show comment
Hide comment
@zwarich

zwarich Jul 9, 2014

Contributor

@stepancheg So what does the grammar for your proposed construct look like? You have to add a new nonterminal that is neither an expression nor a pattern.

Contributor

zwarich commented Jul 9, 2014

@stepancheg So what does the grammar for your proposed construct look like? You have to add a new nonterminal that is neither an expression nor a pattern.

@stepancheg

This comment has been minimized.

Show comment
Hide comment
@stepancheg

stepancheg Jul 10, 2014

@zwarich

You have to invent a new nonterminal that is neither an expression nor a pattern

Sorry, didn't understand that part.

So what does the grammar for your proposed construct look like?

is-expr should be a regular expression. Grammar could be something like this:

if_cond: or_expr
or_expr: and_expr ('||' and_expr)*
and_expr: not_expr ('&&' not_expr)*
not_expr: '!'? (comparison | is_expr)
comparison: ... // down to term
is_expr: expr 'is' match_pat
match_pat: ... // used in match grammar

There's no special treatment of if ... is at parser level. However, typechecker must ensure that either each variable bound in is_expr is either anonymous (e. g. Some(_)) or a part of if_conf and assigned exactly once.

stepancheg commented Jul 10, 2014

@zwarich

You have to invent a new nonterminal that is neither an expression nor a pattern

Sorry, didn't understand that part.

So what does the grammar for your proposed construct look like?

is-expr should be a regular expression. Grammar could be something like this:

if_cond: or_expr
or_expr: and_expr ('||' and_expr)*
and_expr: not_expr ('&&' not_expr)*
not_expr: '!'? (comparison | is_expr)
comparison: ... // down to term
is_expr: expr 'is' match_pat
match_pat: ... // used in match grammar

There's no special treatment of if ... is at parser level. However, typechecker must ensure that either each variable bound in is_expr is either anonymous (e. g. Some(_)) or a part of if_conf and assigned exactly once.

@kballard

This comment has been minimized.

Show comment
Hide comment
@kballard

kballard Jul 10, 2014

Contributor

@stepancheg You haven't addressed my primary objection to is, which is that binding a new variable on the right-hand side of an operator is excessively strange. It also produces a scope that's quite hard to track, because presumably if x is Some(y) && y == 13 { ... } should be valid, but if (x is Some(y) && y == 13) && foo(y) { ... } must be illegal. So the bound variable is visible to all subsequent expressions chained from &&, but it's not visible outside an enclosing expression (e.g. the () group), and it also can't be visible to any subsequent patterns chained with ||. But conversely it is again visible to the body of the if statement. And even considering how other non-boolean expressions interact is confusing.

I think this boils down to conflating an expression with a construct that has the power to bind a variable. Today the only ways to do that are with a declaration (an item or a slot (e.g. a let-binding)) or as part of a match arm, in which any bound variables cannot escape the associated match expression (or even the match arm, i.e. into other match arms). But you're trying to define an expression that does explicitly leak its let-bindings out into the surrounding code, but only under very specific circumstances (i.e. when used in the conditional of an if expression, depending on certain rules around && and || and the complete lack of other operators or expressions being involved). This makes it extremely hard to reason about or to predict the behavior of any complex bit of code that uses the operator.

Contributor

kballard commented Jul 10, 2014

@stepancheg You haven't addressed my primary objection to is, which is that binding a new variable on the right-hand side of an operator is excessively strange. It also produces a scope that's quite hard to track, because presumably if x is Some(y) && y == 13 { ... } should be valid, but if (x is Some(y) && y == 13) && foo(y) { ... } must be illegal. So the bound variable is visible to all subsequent expressions chained from &&, but it's not visible outside an enclosing expression (e.g. the () group), and it also can't be visible to any subsequent patterns chained with ||. But conversely it is again visible to the body of the if statement. And even considering how other non-boolean expressions interact is confusing.

I think this boils down to conflating an expression with a construct that has the power to bind a variable. Today the only ways to do that are with a declaration (an item or a slot (e.g. a let-binding)) or as part of a match arm, in which any bound variables cannot escape the associated match expression (or even the match arm, i.e. into other match arms). But you're trying to define an expression that does explicitly leak its let-bindings out into the surrounding code, but only under very specific circumstances (i.e. when used in the conditional of an if expression, depending on certain rules around && and || and the complete lack of other operators or expressions being involved). This makes it extremely hard to reason about or to predict the behavior of any complex bit of code that uses the operator.

@stepancheg

This comment has been minimized.

Show comment
Hide comment
@stepancheg

stepancheg Jul 10, 2014

@kballard

You haven't addressed my primary objection to is, which is that binding a new variable on the right-hand side of an operator is excessively strange.

Well, I must admit, is is at least unusual, and precise rules of is are significantly more complex than rules of if let.

Probably, all those complex rules are not really needed in practice (or not needed in the first version). If so, I have much simpler, bikeshed, proposal: take yours proposal, and replace

if let PAT = EXPR { BODY }

with

if EXPR is PAT { BODY }

It is almost as simple, as if let, and has advantages:

  • it can be upgraded to full-featured is expression if there will be demand for it
  • it is not confusing. If-let looks like C' assign and use if (a = expr) { ... }, but it is not.

stepancheg commented Jul 10, 2014

@kballard

You haven't addressed my primary objection to is, which is that binding a new variable on the right-hand side of an operator is excessively strange.

Well, I must admit, is is at least unusual, and precise rules of is are significantly more complex than rules of if let.

Probably, all those complex rules are not really needed in practice (or not needed in the first version). If so, I have much simpler, bikeshed, proposal: take yours proposal, and replace

if let PAT = EXPR { BODY }

with

if EXPR is PAT { BODY }

It is almost as simple, as if let, and has advantages:

  • it can be upgraded to full-featured is expression if there will be demand for it
  • it is not confusing. If-let looks like C' assign and use if (a = expr) { ... }, but it is not.
@kballard

This comment has been minimized.

Show comment
Hide comment
@kballard

kballard Jul 10, 2014

Contributor

@stepancheg That is certainly much cleaner than your previous approach, but here are the downsides:

  1. It prevents using is as an operator. You cannot take this proposal and expand it to a full-featured is operator without reintroducing all of the issues I raised before (most importantly, the very bizarre rules around the scope of any bound variables).
  2. It contradicts all existing mechanisms for binding variables in that the bound variables appear on the right-hand side of the delimiter instead of the left-hand side, e,g if foo() is Some(x). The natural way to read this is to treat x as a pre-existing variable and match on its value, rather than treating it as a new let-binding.
  3. It reintroduces ambiguity with braced blocks and struct patterns. if let Foo { x } = foo() { ... } is legal with my proposal, but yours makes that if foo() is Foo { x } { ... } and that's an ambiguous parse. It's currently illegal to wrap a struct pattern like that in parentheses so you cannot resolve this as if foo() is (Foo { x }) { ... } without changing how patterns are parsed.

All in all, I'd rather treat is as a potential operator with the semantics of the matches!() macro from rust-lang/rust#14685.

Contributor

kballard commented Jul 10, 2014

@stepancheg That is certainly much cleaner than your previous approach, but here are the downsides:

  1. It prevents using is as an operator. You cannot take this proposal and expand it to a full-featured is operator without reintroducing all of the issues I raised before (most importantly, the very bizarre rules around the scope of any bound variables).
  2. It contradicts all existing mechanisms for binding variables in that the bound variables appear on the right-hand side of the delimiter instead of the left-hand side, e,g if foo() is Some(x). The natural way to read this is to treat x as a pre-existing variable and match on its value, rather than treating it as a new let-binding.
  3. It reintroduces ambiguity with braced blocks and struct patterns. if let Foo { x } = foo() { ... } is legal with my proposal, but yours makes that if foo() is Foo { x } { ... } and that's an ambiguous parse. It's currently illegal to wrap a struct pattern like that in parentheses so you cannot resolve this as if foo() is (Foo { x }) { ... } without changing how patterns are parsed.

All in all, I'd rather treat is as a potential operator with the semantics of the matches!() macro from rust-lang/rust#14685.

@stepancheg

This comment has been minimized.

Show comment
Hide comment
@stepancheg

stepancheg Jul 10, 2014

@kballard

  1. It reintroduces ambiguity with braced blocks and struct patterns

That's a problem, thanks for pointing it out.

stepancheg commented Jul 10, 2014

@kballard

  1. It reintroduces ambiguity with braced blocks and struct patterns

That's a problem, thanks for pointing it out.

@Valloric

This comment has been minimized.

Show comment
Hide comment
@Valloric

Valloric Jul 10, 2014

This RFC is excellent. We need to improve Rust's ergonomics; lately it's been going down.

Valloric commented Jul 10, 2014

This RFC is excellent. We need to improve Rust's ergonomics; lately it's been going down.

@nielsle

This comment has been minimized.

Show comment
Hide comment
@nielsle

nielsle Jul 10, 2014

Perhaps "is" could be replaced by @ in the version proposed by @stepancheg . That would be somewhat consistent with match statements and it would not require a new keyword..

if foo  @ Some(x) {
    doSomethingWith(x)
}

EDIT: Changed => to @

nielsle commented Jul 10, 2014

Perhaps "is" could be replaced by @ in the version proposed by @stepancheg . That would be somewhat consistent with match statements and it would not require a new keyword..

if foo  @ Some(x) {
    doSomethingWith(x)
}

EDIT: Changed => to @

@hatahet

This comment has been minimized.

Show comment
Hide comment
@hatahet

hatahet Jul 10, 2014

I like the fact that it is easy to visually notice a match expression, and immediately be able to tell that there is an exhaustive matching going on. It may be harder to do with if let expression. Furthermore, how would this interact with HKT + do expressions? Wouldn't the latter reduce the "burden" of plain match expressions?

hatahet commented Jul 10, 2014

I like the fact that it is easy to visually notice a match expression, and immediately be able to tell that there is an exhaustive matching going on. It may be harder to do with if let expression. Furthermore, how would this interact with HKT + do expressions? Wouldn't the latter reduce the "burden" of plain match expressions?

@kballard

This comment has been minimized.

Show comment
Hide comment
@kballard

kballard Jul 10, 2014

Contributor

@hatahet Well, the point of an if let is that it's not exhaustive, so I guess I'm not sure what point you're trying to make.

As for HKT + do, could you elaborate? I'm unaware of any concrete proposals for HKT in Rust, or for do expressions, so I can't very well talk about how if let would interact. But since if let is isomorphic to a subset of match expressions, I don't expect there to be any conflict with any future Rust proposals (as the functionality of match is pretty well established at this point and is exceedingly unlikely to change in any meaningful fashion).

If HKT + do extends the behavior of match in any fashion, it's plausible that if let could be similarly extended, but that depends entirely on what changes HKT + do actually makes to match.

Contributor

kballard commented Jul 10, 2014

@hatahet Well, the point of an if let is that it's not exhaustive, so I guess I'm not sure what point you're trying to make.

As for HKT + do, could you elaborate? I'm unaware of any concrete proposals for HKT in Rust, or for do expressions, so I can't very well talk about how if let would interact. But since if let is isomorphic to a subset of match expressions, I don't expect there to be any conflict with any future Rust proposals (as the functionality of match is pretty well established at this point and is exceedingly unlikely to change in any meaningful fashion).

If HKT + do extends the behavior of match in any fashion, it's plausible that if let could be similarly extended, but that depends entirely on what changes HKT + do actually makes to match.

@SimonSapin

This comment has been minimized.

Show comment
Hide comment
@SimonSapin

SimonSapin Jul 10, 2014

Contributor

if let is great. +1

Contributor

SimonSapin commented Jul 10, 2014

if let is great. +1

@thehydroimpulse

This comment has been minimized.

Show comment
Hide comment
@thehydroimpulse

thehydroimpulse commented Jul 10, 2014

+1

@cburgdorf

This comment has been minimized.

Show comment
Hide comment
@cburgdorf

cburgdorf commented Jul 10, 2014

+1

@stepancheg

This comment has been minimized.

Show comment
Hide comment
@stepancheg

stepancheg Jul 10, 2014

@kballard

OK, I came up with another bikeshed proposal: take your proposal, and replace

if let PAT = EXPR { BODY }

with

if EXPR is PAT => { BODY }

New syntax/semantics has following properties:

  • if .. is solves parser ambiguity in my previous proposal
  • fixes problem that let xxx = yyy means different things in different contexts (exhaustive vs. non-exhaustive matching)
  • looks more natural than if let
  • Swift has if let with completely different meaning
  • order of EXPR and PAT is the same as in match expr, unlike if let statement
  • => is the same as in match expression

Addressing two your other issues:

  1. is expression that does not bind variables has little value. In another words, is-expression should either bind variables, or not exist at all.
  2. in if ... is order or EXPR and PAT is the same as in match expr

stepancheg commented Jul 10, 2014

@kballard

OK, I came up with another bikeshed proposal: take your proposal, and replace

if let PAT = EXPR { BODY }

with

if EXPR is PAT => { BODY }

New syntax/semantics has following properties:

  • if .. is solves parser ambiguity in my previous proposal
  • fixes problem that let xxx = yyy means different things in different contexts (exhaustive vs. non-exhaustive matching)
  • looks more natural than if let
  • Swift has if let with completely different meaning
  • order of EXPR and PAT is the same as in match expr, unlike if let statement
  • => is the same as in match expression

Addressing two your other issues:

  1. is expression that does not bind variables has little value. In another words, is-expression should either bind variables, or not exist at all.
  2. in if ... is order or EXPR and PAT is the same as in match expr
@kballard

This comment has been minimized.

Show comment
Hide comment
@kballard

kballard Jul 11, 2014

Contributor

@stepancheg What concrete problems do you see with if let that you are trying to solve? You said yours "looks more natural than if let", but that's purely subjective, and I happen to disagree.

Your new proposal doesn't solve any of my big objections to your previous ones, which is the usage of is as if it were an operator (which it isn't, and can't ever be with this proposal), the fact that variables are bound on the RHS which is inconsistent with the rest of Rust and also quite unintuitive, and the fact that your syntax does not make it obvious that this is not a normal if statement.

I'm happy to go into detail on all of this, but this is ground we've covered before and I don't think it's worthwhile. So here's a short rebuttal of the brand new points you've made:

  • let xxx = yyy being generalized from irrefutable to refutable patterns is the entire point of having this construct. I wholly disagree with your statement that it's an advantage to avoid this. I believe this generalization is in fact an intuitive result of the idea of combining let and if together.
  • Swift's if let is very nearly the same meaning. It is a specialization of the if let I've proposed here, behaving as though it were my proposed if let with an implicit Some(...) wrapped around the pattern. Swift's if let foo = bar() { ... } is the same as my if let Some(foo) = bar() { ... }
  • order of EXPR and PAT are not the same as match. match is a hierarchical construct, where the EXPR occurs at a higher level, and all the PATs occur inside a nested block. Your construct puts them left-to-right at the same hierarchical level.
  • I completely disagree that an is operator that doesn't bind variables has little value. In fact, I'm rather tempted to write up an RFC for an is operator as well (although I'll hold off for the time being). Such an operator would obsolete all the type-specific enum variant tests, e.g. Option.is_some() and Result.is_ok(), it would provide a mechanism for doing easy enum variant testing without deriving Eq (which is important for enums that cannot easily derive Eq), and it would provide an easy way for doing equality testing of values embedded (perhaps several levels deep) in enums without having to do a destructuring bind first. As a trivial example, write_err is Err(IoError{ kind: EndOfFile, .. }).

I appreciate the thought you've put into this, but I'd rather talk about what problems you think my proposal has (and why you think they're problems), than continue to bikeshed alternative syntaxes.

Contributor

kballard commented Jul 11, 2014

@stepancheg What concrete problems do you see with if let that you are trying to solve? You said yours "looks more natural than if let", but that's purely subjective, and I happen to disagree.

Your new proposal doesn't solve any of my big objections to your previous ones, which is the usage of is as if it were an operator (which it isn't, and can't ever be with this proposal), the fact that variables are bound on the RHS which is inconsistent with the rest of Rust and also quite unintuitive, and the fact that your syntax does not make it obvious that this is not a normal if statement.

I'm happy to go into detail on all of this, but this is ground we've covered before and I don't think it's worthwhile. So here's a short rebuttal of the brand new points you've made:

  • let xxx = yyy being generalized from irrefutable to refutable patterns is the entire point of having this construct. I wholly disagree with your statement that it's an advantage to avoid this. I believe this generalization is in fact an intuitive result of the idea of combining let and if together.
  • Swift's if let is very nearly the same meaning. It is a specialization of the if let I've proposed here, behaving as though it were my proposed if let with an implicit Some(...) wrapped around the pattern. Swift's if let foo = bar() { ... } is the same as my if let Some(foo) = bar() { ... }
  • order of EXPR and PAT are not the same as match. match is a hierarchical construct, where the EXPR occurs at a higher level, and all the PATs occur inside a nested block. Your construct puts them left-to-right at the same hierarchical level.
  • I completely disagree that an is operator that doesn't bind variables has little value. In fact, I'm rather tempted to write up an RFC for an is operator as well (although I'll hold off for the time being). Such an operator would obsolete all the type-specific enum variant tests, e.g. Option.is_some() and Result.is_ok(), it would provide a mechanism for doing easy enum variant testing without deriving Eq (which is important for enums that cannot easily derive Eq), and it would provide an easy way for doing equality testing of values embedded (perhaps several levels deep) in enums without having to do a destructuring bind first. As a trivial example, write_err is Err(IoError{ kind: EndOfFile, .. }).

I appreciate the thought you've put into this, but I'd rather talk about what problems you think my proposal has (and why you think they're problems), than continue to bikeshed alternative syntaxes.

@Valloric

This comment has been minimized.

Show comment
Hide comment
@Valloric

Valloric Jul 11, 2014

In fact, I'm rather tempted to write up an RFC for an is operator as well [...]

@kballard Your description of that potential RFC sounds very nice. Please write that up at some point!

Valloric commented Jul 11, 2014

In fact, I'm rather tempted to write up an RFC for an is operator as well [...]

@kballard Your description of that potential RFC sounds very nice. Please write that up at some point!

@jfager

This comment has been minimized.

Show comment
Hide comment
@jfager

jfager Jul 11, 2014

*Edit: sorry, posted this before I was done writing it.

Overall I'm +1, but just to clarify from your response to @stepancheg's proposal, this wouldn't allow things like the following, correct?

if let Some(x) = foo && bar {
    doSomethingWith(x)
}

if bar && let Some(x) = foo {
    doSomethingWith(x)
}

if let Some(_) = foo || bar {
    doSomething()
}

That seems to be the drawback to me, that people will see if and want an arbitrary conditional.

You could almost simulate the && cases, except you lose the short-circuiting:

if let (Some(x), true) = (foo, bar) {
    doSomethingWith(x)
}

But there's no way to simulate the || case.

jfager commented Jul 11, 2014

*Edit: sorry, posted this before I was done writing it.

Overall I'm +1, but just to clarify from your response to @stepancheg's proposal, this wouldn't allow things like the following, correct?

if let Some(x) = foo && bar {
    doSomethingWith(x)
}

if bar && let Some(x) = foo {
    doSomethingWith(x)
}

if let Some(_) = foo || bar {
    doSomething()
}

That seems to be the drawback to me, that people will see if and want an arbitrary conditional.

You could almost simulate the && cases, except you lose the short-circuiting:

if let (Some(x), true) = (foo, bar) {
    doSomethingWith(x)
}

But there's no way to simulate the || case.

@stepancheg

This comment has been minimized.

Show comment
Hide comment
@stepancheg

stepancheg Jul 11, 2014

@kballard

What concrete problems do you see with if let that you are trying to solve?

Largest problem with if let is that it is counter-intuitive.

let x = y;

is a statement, unconditional assignment. It is not an expression, and x must be exhaustive pattern.

However, when let x = y is attached to if:

if let x = y { ... }

meaning of let x = y part becomes different: it is boolean expression now (not a real boolean expression, but it feels like that), and x is no longer required to be exhaustive.

The same syntax construct let ... = ... has different meaning in different contexts. I think it is a problem.

stepancheg commented Jul 11, 2014

@kballard

What concrete problems do you see with if let that you are trying to solve?

Largest problem with if let is that it is counter-intuitive.

let x = y;

is a statement, unconditional assignment. It is not an expression, and x must be exhaustive pattern.

However, when let x = y is attached to if:

if let x = y { ... }

meaning of let x = y part becomes different: it is boolean expression now (not a real boolean expression, but it feels like that), and x is no longer required to be exhaustive.

The same syntax construct let ... = ... has different meaning in different contexts. I think it is a problem.

@dobkeratops

This comment has been minimized.

Show comment
Hide comment
@dobkeratops

dobkeratops Jul 11, 2014

So its more like if_let ... than if (let...) .. but it works for me, its similar to reading if (auto *p=...){ .. use p..}, and its kind to all the people who will be learning swift due to the popularity of iOS

dobkeratops commented Jul 11, 2014

So its more like if_let ... than if (let...) .. but it works for me, its similar to reading if (auto *p=...){ .. use p..}, and its kind to all the people who will be learning swift due to the popularity of iOS

@jsanders

This comment has been minimized.

Show comment
Hide comment
@jsanders

jsanders Jul 11, 2014

I don't think @dobkeratops meant to suggest this, but if this is confusing to some people (so far just @stepancheg) as two keywords combined into one behavior that is different from either of their separate behaviors, what about introducing a new keyword ifletthat does just this? I personally find if let nicer and the swift precedent is a good thing.

jsanders commented Jul 11, 2014

I don't think @dobkeratops meant to suggest this, but if this is confusing to some people (so far just @stepancheg) as two keywords combined into one behavior that is different from either of their separate behaviors, what about introducing a new keyword ifletthat does just this? I personally find if let nicer and the swift precedent is a good thing.

@kballard

This comment has been minimized.

Show comment
Hide comment
@kballard

kballard Jul 11, 2014

Contributor

@jfager: Correct, you can't mix && or || into this. The || case would actually be doable with the theoretical is operator. The && case can be solved by creating a free function (say, in std::option) that turns a bool into an Option<()> so you can then use Option.and() to get the short-circuiting && behavior.

Remember, if let is isomorphic to a subset of match. You can't do short-circuiting && or || with match either.

@dobkeratops @jsanders Yes it's effectively an iflet command, except it's written as if let, which I prefer.

Contributor

kballard commented Jul 11, 2014

@jfager: Correct, you can't mix && or || into this. The || case would actually be doable with the theoretical is operator. The && case can be solved by creating a free function (say, in std::option) that turns a bool into an Option<()> so you can then use Option.and() to get the short-circuiting && behavior.

Remember, if let is isomorphic to a subset of match. You can't do short-circuiting && or || with match either.

@dobkeratops @jsanders Yes it's effectively an iflet command, except it's written as if let, which I prefer.

dhardy pushed a commit to dhardy/rfcs that referenced this pull request Jul 23, 2014

@Manishearth

This comment has been minimized.

Show comment
Hide comment
@Manishearth

Manishearth Jul 28, 2014

Member

+1

For those who say that this is non-exhaustive: It's not meant to be. You use it when you explicitly want to test for a single variant (or a small set if you're using the proposed | syntax). Everyone already uses the classic

match foo {
  Bar(x) => {/*...*/},
  _ => {}
}

all over the place, if introducing a new variant breaks if-let, it also breaks these matches.

I agree that a match statement makes you explicitly think about exhaustiveness, but so does if let -- it's pretty clear that you're talking about one variant, and all the rest are to be ignored. I can't imagine any case where newbies/inexperienced programmers would use if let but not match due to the explicit wildcard in match.

Member

Manishearth commented Jul 28, 2014

+1

For those who say that this is non-exhaustive: It's not meant to be. You use it when you explicitly want to test for a single variant (or a small set if you're using the proposed | syntax). Everyone already uses the classic

match foo {
  Bar(x) => {/*...*/},
  _ => {}
}

all over the place, if introducing a new variant breaks if-let, it also breaks these matches.

I agree that a match statement makes you explicitly think about exhaustiveness, but so does if let -- it's pretty clear that you're talking about one variant, and all the rest are to be ignored. I can't imagine any case where newbies/inexperienced programmers would use if let but not match due to the explicit wildcard in match.

@dhardy

This comment has been minimized.

Show comment
Hide comment
@dhardy

dhardy Jul 28, 2014

Contributor

+1

Having thought about it a bit, I prefer if let over my own proposal: it is more generic, does not require a new keyword, is already used in another language, and its meaning is somewhat self-explanatory.

I somewhat dislike the use of = to mean both "if PAT matches EXPR" and "let VAR be ...": = is usually only used for assignment. I can think of some alternatives, but none of them are really better: with PAT in EXPR (like in my RFC, inspired by the Python with syntax), with PAT from EXPR (as @kballard pointed out, in is inappropriate; from may be better but I don't think is a good idea), match PAT against EXPR, ... so probably not worth revising.

So, why I think if let or similar is important: it gives match-like semantics with a different logic structure which isn't otherwise available (e.g. see the example in the #181 proposal).

Contributor

dhardy commented Jul 28, 2014

+1

Having thought about it a bit, I prefer if let over my own proposal: it is more generic, does not require a new keyword, is already used in another language, and its meaning is somewhat self-explanatory.

I somewhat dislike the use of = to mean both "if PAT matches EXPR" and "let VAR be ...": = is usually only used for assignment. I can think of some alternatives, but none of them are really better: with PAT in EXPR (like in my RFC, inspired by the Python with syntax), with PAT from EXPR (as @kballard pointed out, in is inappropriate; from may be better but I don't think is a good idea), match PAT against EXPR, ... so probably not worth revising.

So, why I think if let or similar is important: it gives match-like semantics with a different logic structure which isn't otherwise available (e.g. see the example in the #181 proposal).

@bharrisau

This comment has been minimized.

Show comment
Hide comment
@bharrisau

bharrisau Jul 28, 2014

But it IS assignment. A refutable assignment.

bharrisau commented Jul 28, 2014

But it IS assignment. A refutable assignment.

@dhardy

This comment has been minimized.

Show comment
Hide comment
@dhardy

dhardy Jul 28, 2014

Contributor

No, it's a conditional and an assignment. And it's definitely not refutable.

Contributor

dhardy commented Jul 28, 2014

No, it's a conditional and an assignment. And it's definitely not refutable.

@bharrisau

This comment has been minimized.

Show comment
Hide comment
@bharrisau

bharrisau Jul 28, 2014

It's a destructuring assignment with a refutable pattern. Unless I'm missing something.

It isn't an if let, it is if let. let requires an irrefutable pattern, if let can have a refutable one. You just need to provide the block to execute if the assignment succeeds (so it looks like an if).

bharrisau commented Jul 28, 2014

It's a destructuring assignment with a refutable pattern. Unless I'm missing something.

It isn't an if let, it is if let. let requires an irrefutable pattern, if let can have a refutable one. You just need to provide the block to execute if the assignment succeeds (so it looks like an if).

@dhardy

This comment has been minimized.

Show comment
Hide comment
@dhardy

dhardy Jul 28, 2014

Contributor

I would rather call it a matchable pattern in a conditional assignment; but if you wish to call it a refutable pattern in a claimed assignment, I guess I can accept that as almost correct English. Not that this argument is going anywhere much.

Contributor

dhardy commented Jul 28, 2014

I would rather call it a matchable pattern in a conditional assignment; but if you wish to call it a refutable pattern in a claimed assignment, I guess I can accept that as almost correct English. Not that this argument is going anywhere much.

@glaebhoerl

This comment has been minimized.

Show comment
Hide comment
@glaebhoerl

glaebhoerl Jul 28, 2014

Contributor

As far as I'm aware "refutable" and "irrefutable" are terms of art, not necessarily used here in their English sense, for patterns which can or cannot fail to match.

Contributor

glaebhoerl commented Jul 28, 2014

As far as I'm aware "refutable" and "irrefutable" are terms of art, not necessarily used here in their English sense, for patterns which can or cannot fail to match.

@kballard

This comment has been minimized.

Show comment
Hide comment
@kballard

kballard Aug 7, 2014

Contributor

Does anyone feel that there's anything from this discussion that needs to be added back to the RFC document?

Contributor

kballard commented Aug 7, 2014

Does anyone feel that there's anything from this discussion that needs to be added back to the RFC document?

@kballard kballard referenced this pull request Aug 25, 2014

Closed

Implement `if let` #16741

@kballard

This comment has been minimized.

Show comment
Hide comment
@kballard

kballard Aug 25, 2014

Contributor

I went ahead and created a patch for this as rust-lang/rust#16741.

Contributor

kballard commented Aug 25, 2014

I went ahead and created a patch for this as rust-lang/rust#16741.

@reem

This comment has been minimized.

Show comment
Hide comment
@reem

reem Aug 25, 2014

I am strongly in favor of this RFC, it makes working with enums, especially those with two variants, much more pleasant.

reem commented Aug 25, 2014

I am strongly in favor of this RFC, it makes working with enums, especially those with two variants, much more pleasant.

@kballard

This comment has been minimized.

Show comment
Hide comment
@kballard

kballard Aug 25, 2014

Contributor

It turns out Swift also supports while let. Is there any benefit to adding that to Rust as well, or should we just stick with if let?

Contributor

kballard commented Aug 25, 2014

It turns out Swift also supports while let. Is there any benefit to adding that to Rust as well, or should we just stick with if let?

@cmr

This comment has been minimized.

Show comment
Hide comment
@cmr

cmr Aug 25, 2014

Member

I've often wanted a keyword for the loop { match foo { ... } } construct.

On Mon, Aug 25, 2014 at 7:18 PM, Kevin Ballard notifications@github.com
wrote:

It turns out Swift also supports while let. Is there any benefit to
adding that to Rust as well, or should we just stick with if let?


Reply to this email directly or view it on GitHub
#160 (comment).

http://octayn.net/

Member

cmr commented Aug 25, 2014

I've often wanted a keyword for the loop { match foo { ... } } construct.

On Mon, Aug 25, 2014 at 7:18 PM, Kevin Ballard notifications@github.com
wrote:

It turns out Swift also supports while let. Is there any benefit to
adding that to Rust as well, or should we just stick with if let?


Reply to this email directly or view it on GitHub
#160 (comment).

http://octayn.net/

@glaebhoerl

This comment has been minimized.

Show comment
Hide comment
@glaebhoerl

glaebhoerl Aug 26, 2014

Contributor

@kballard Interesting. I'm guessing it would be used like so?

while let Some(val) = iter.next() {
    println!("val: {}", val);
}

I actually like this quite a bit, but (given that the discussion about if let has already run its course) it would probably be better to just get if let in first, and propose this in a new RFC.

@cmr As the simplest possible solution, we could even just make a special exception and allow loop match foo { ... } without the double braces.

Contributor

glaebhoerl commented Aug 26, 2014

@kballard Interesting. I'm guessing it would be used like so?

while let Some(val) = iter.next() {
    println!("val: {}", val);
}

I actually like this quite a bit, but (given that the discussion about if let has already run its course) it would probably be better to just get if let in first, and propose this in a new RFC.

@cmr As the simplest possible solution, we could even just make a special exception and allow loop match foo { ... } without the double braces.

@kballard

This comment has been minimized.

Show comment
Hide comment
@kballard

kballard Aug 26, 2014

Contributor

@glaebhoerl Yeah that's how it would work.

loop match foo { ... } looks pretty weird to me. That looks like loop is being used as some sort of prefix modifier, which would suggest it should work for other expressions too.

Contributor

kballard commented Aug 26, 2014

@glaebhoerl Yeah that's how it would work.

loop match foo { ... } looks pretty weird to me. That looks like loop is being used as some sort of prefix modifier, which would suggest it should work for other expressions too.

@mahkoh

This comment has been minimized.

Show comment
Hide comment
@mahkoh

mahkoh Aug 26, 2014

Contributor

+1

match maybe_x() {
    Some(x) => { ... },
    _ => match maybe_y() {
        Some(y) => { ... },
        _ => { ... },
    },
}

is way too noisy.

Contributor

mahkoh commented Aug 26, 2014

+1

match maybe_x() {
    Some(x) => { ... },
    _ => match maybe_y() {
        Some(y) => { ... },
        _ => { ... },
    },
}

is way too noisy.

@glaebhoerl

This comment has been minimized.

Show comment
Hide comment
@glaebhoerl

glaebhoerl Aug 26, 2014

Contributor

@kballard Yeah, maybe. But weighed against the alternative of adding a new keyword? More economical to just make loop match be the "keyword". Kind of similar to the logic for if let. (Or of course, could just do nothing and keep requiring nested blocks. Not a formal proposal, just brainstorming "if we want to solve this, how could we solve it" w.r.t. @cmr's desire.)

Contributor

glaebhoerl commented Aug 26, 2014

@kballard Yeah, maybe. But weighed against the alternative of adding a new keyword? More economical to just make loop match be the "keyword". Kind of similar to the logic for if let. (Or of course, could just do nothing and keep requiring nested blocks. Not a formal proposal, just brainstorming "if we want to solve this, how could we solve it" w.r.t. @cmr's desire.)

@kballard

This comment has been minimized.

Show comment
Hide comment
@kballard

kballard Aug 26, 2014

Contributor

What new keyword?

Contributor

kballard commented Aug 26, 2014

What new keyword?

@glaebhoerl

This comment has been minimized.

Show comment
Hide comment
@glaebhoerl

glaebhoerl Aug 26, 2014

Contributor

Quoth @cmr:

I've often wanted a keyword for the loop { match foo { ... } } construct.

Contributor

glaebhoerl commented Aug 26, 2014

Quoth @cmr:

I've often wanted a keyword for the loop { match foo { ... } } construct.

@kballard

This comment has been minimized.

Show comment
Hide comment
@kballard

kballard Aug 26, 2014

Contributor

@glaebhoerl Yes, and that's what the while let ... I mentioned would do. No new keyword, just a desugaring of an existing-but-illegal keyword combination (just like if let).

Contributor

kballard commented Aug 26, 2014

@glaebhoerl Yes, and that's what the while let ... I mentioned would do. No new keyword, just a desugaring of an existing-but-illegal keyword combination (just like if let).

@alexcrichton

This comment has been minimized.

Show comment
Hide comment
@alexcrichton

alexcrichton Aug 27, 2014

Member

This was discussed in today's meeting.

Concerns were brought up about how a simple AST rewrite can yield surprising results in, but it was also decided that an implementation not specc'd to be an AST rewrite would not be accepted. Due to lingering uncertainties, we decided to merge this with the caveat that it is all initially behind a feature gate. @kballard, can you update the RFC to reflect this?

We also decided to postpone something like while let to a future RFC rather than asking for inclusion in this one.

Member

alexcrichton commented Aug 27, 2014

This was discussed in today's meeting.

Concerns were brought up about how a simple AST rewrite can yield surprising results in, but it was also decided that an implementation not specc'd to be an AST rewrite would not be accepted. Due to lingering uncertainties, we decided to merge this with the caveat that it is all initially behind a feature gate. @kballard, can you update the RFC to reflect this?

We also decided to postpone something like while let to a future RFC rather than asking for inclusion in this one.

Add note about feature-gate
Also fix a couple of typos.
@kballard

This comment has been minimized.

Show comment
Hide comment
@kballard

kballard Aug 27, 2014

Contributor

@alexcrichton Note about feature gate added.

Contributor

kballard commented Aug 27, 2014

@alexcrichton Note about feature gate added.

@alexcrichton alexcrichton referenced this pull request Aug 27, 2014

Closed

Implement if-let #16779

@alexcrichton alexcrichton merged commit d7a1dba into rust-lang:master Aug 27, 2014

@kballard kballard deleted the kballard:if_let branch Aug 27, 2014

stepancheg added a commit to stepancheg/rust-protobuf that referenced this pull request Dec 6, 2014

Use `if let` statement in generated code
(I'd like Rust to have more generic `is` expression:
rust-lang/rfcs#160 (comment) )
@Biluoshilang

This comment has been minimized.

Show comment
Hide comment
@Biluoshilang

Biluoshilang Oct 10, 2015

find no goods for it

Biluoshilang commented Oct 10, 2015

find no goods for it

@lilijreey

This comment has been minimized.

Show comment
Hide comment
@lilijreey

lilijreey Nov 14, 2017

I think it is so bad Idea.
match Option Type just need add maroc. like this.
if Some(x) match V {
do(x)
}

lilijreey commented Nov 14, 2017

I think it is so bad Idea.
match Option Type just need add maroc. like this.
if Some(x) match V {
do(x)
}

@chris-morgan

This comment has been minimized.

Show comment
Hide comment
@chris-morgan

chris-morgan Nov 14, 2017

Member

@lilijreey This landed over three years ago, well before Rust 1.0. It’s not going away. We like it, anyway, and it’s consistent with the rest of the language. (Your suggestion also leads to parsing difficulties, as if can then be followed by a pattern or an expression.)

Member

chris-morgan commented Nov 14, 2017

@lilijreey This landed over three years ago, well before Rust 1.0. It’s not going away. We like it, anyway, and it’s consistent with the rest of the language. (Your suggestion also leads to parsing difficulties, as if can then be followed by a pattern or an expression.)

@najamelan

This comment has been minimized.

Show comment
Hide comment
@najamelan

najamelan Dec 7, 2017

I think making the syntax more concise is an important thing, since rust has a lot of boilerplate, which hinders readablility. Hence I was wondering why the following doesn't work:

if let Some( content_type ) = res_in.headers.get( "Content-Type" )
&& let Ok  ( parsed       ) = content_type.parse()
{
   out = out.with_header( hyper::header::ContentType( parsed ) );
}

This expresses "Run this code block if all of these conditions are met". As opposed to:

if let Some( content_type ) = res_in.headers.get( "Content-Type" ) {
if let Ok  ( parsed       ) = content_type.parse()
{
   out = out.with_header( hyper::header::ContentType( parsed ) );
}}

This isn't much longer to write, but what does it express? Confusion? To not be confusing to read, it should be written as:

if let Some( content_type ) = res_in.headers.get( "Content-Type" )
{
   if let Ok( parsed ) = content_type.parse()
   {
      out = out.with_header( hyper::header::ContentType( parsed ) );
   }
}

Which now is a lot more noisy. Seeing this type of construct in any other language indicates "poor condition logic probably hiding a bug", but in rust the language just obliges us to write stuff like this.

The contrast between the two paradigms will grow if you have more conditions to chain up.

najamelan commented Dec 7, 2017

I think making the syntax more concise is an important thing, since rust has a lot of boilerplate, which hinders readablility. Hence I was wondering why the following doesn't work:

if let Some( content_type ) = res_in.headers.get( "Content-Type" )
&& let Ok  ( parsed       ) = content_type.parse()
{
   out = out.with_header( hyper::header::ContentType( parsed ) );
}

This expresses "Run this code block if all of these conditions are met". As opposed to:

if let Some( content_type ) = res_in.headers.get( "Content-Type" ) {
if let Ok  ( parsed       ) = content_type.parse()
{
   out = out.with_header( hyper::header::ContentType( parsed ) );
}}

This isn't much longer to write, but what does it express? Confusion? To not be confusing to read, it should be written as:

if let Some( content_type ) = res_in.headers.get( "Content-Type" )
{
   if let Ok( parsed ) = content_type.parse()
   {
      out = out.with_header( hyper::header::ContentType( parsed ) );
   }
}

Which now is a lot more noisy. Seeing this type of construct in any other language indicates "poor condition logic probably hiding a bug", but in rust the language just obliges us to write stuff like this.

The contrast between the two paradigms will grow if you have more conditions to chain up.

@chris-morgan

This comment has been minimized.

Show comment
Hide comment
@chris-morgan

chris-morgan Dec 7, 2017

Member

@najamelan This RFC was finished over three years ago. Any changes such as you suggest are additions to the language which will need to be in the form of a new RFC.

Member

chris-morgan commented Dec 7, 2017

@najamelan This RFC was finished over three years ago. Any changes such as you suggest are additions to the language which will need to be in the form of a new RFC.

@najamelan

This comment has been minimized.

Show comment
Hide comment
@najamelan

najamelan Dec 7, 2017

Ok, I'm quite new to rust, but if people think this has a chance of getting accepted, I'd be willing to write the RFC.

najamelan commented Dec 7, 2017

Ok, I'm quite new to rust, but if people think this has a chance of getting accepted, I'd be willing to write the RFC.

@dhardy

This comment has been minimized.

Show comment
Hide comment
@dhardy

dhardy Dec 7, 2017

Contributor

If you want another channel for feedback before writing an RFC, use https://internals.rust-lang.org/

Contributor

dhardy commented Dec 7, 2017

If you want another channel for feedback before writing an RFC, use https://internals.rust-lang.org/

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