Rfcs: if let && bool

Created on 20 Apr 2018  Â·  13Comments  Â·  Source: rust-lang/rfcs

First, there are currently 474 issues|PRs here that contain "if let", so I hope I'm not duplicating something already discussed.

This idea is quite close to https://github.com/rust-lang/rfcs/issues/929, but instead of allowing to && lets, it'd allow to && with a bool.

Basically, the idea looks like this:

if let Some(x) = y && x > 42 {
    println!("foo");
} else {
    println!("bar");
}

// Would be equivalent to:
if let Some(x) = y {
    if x > 42 {
        println!("foo");
    } else {
        println!("bar");
    }
} else {
    println!("bar");
}

This pattern is something that frequently occurred to me, and I'm finding it painful to always look for workarounds.

The constraints to this pattern would be:

  • Only && could be used after a let, especially if a || is wanted it must be as a child node of the && (required so that the RHS could be evaluated with the let binding in scope)
  • For the time being, it is restricted to one let and one or more && with bools, leaving ideas like if let Some((a, b)) = x && a > 42 && let Some(c) = b.unwrap() for later (modulo #929)

What do you think about this idea?

T-lang

Most helpful comment

The [expr] is [pat] syntax that @petrochenkov has been working on hasn't been RFCed yet,

Overall, I think it'd likely be better to first RFC the [expr] is [pat]

There are some holidays in May, so I hope to write an EXRP is PAT RFC based on the implementation experience.
After "tasting" the feature while porting rustmt to my experimental branch I now really miss it while writing normal Rust code accepted by the standard compiler.

All 13 comments

Actually this seems to have been discussed many times:)

(BTW, if the condition does only a single pattern match, a workaround can simply be a match with a guard. I think that's why multiple lets in if is especially demanded.)

match x {
    Some(x) if x > 42 => foo,
    _ => bar,
}

Well, I had come upon #2260, and that's the reason why I wanted to just stay with the simple if let [pat] = [var] && [cond], in order to keep things simple.

Actually, reading https://github.com/rust-lang/rfcs/pull/2175 makes me think the syntax may be better like:

if let Some(x) = y if x > 42 {
    foo
} else {
    bar
}

This way 1/ the syntax is exactly the same as that of match guards, which is more consistent, and 2/ it avoids the issue of having to set an operator precedence to let in if let true = x && false {.

However it's then going back into the debate of #2260, and I hoped a reduced scope might help in getting at least something? Or maybe I should just wait for things to settle a bit and then @petrochenkov's implementation will help things move forward, despite the RFC being closed?

Hey there. Me and @scottmcm wrote #2260. Since then #2046 has been merged and is getting implemented soon (by @est31).

If you want to propose if let [pat] = [expr] && [expr], which is my preferred syntax, then you should consider how you deal with any potential ambiguities. The [expr] is [pat] syntax that @petrochenkov has been working on hasn't been RFCed yet, so we still need to discuss the design and see if we want to go with [expr] is [pat] or not.

Overall, I think it'd likely be better to first RFC the [expr] is [pat] (if anything because there is already a tentative implementation of it), and depending on the result come back here.

About dealing with ambiguities, I think that in order to stay retro-compatible the only way might be to force parenthesizing the let block (as if let x = a && b already parses and has a meaning currently).

So I'd see it like this:

if (let BINDING = EXPR1) && EXPR2 {
    STMT3
} else {
    STMT4
}

This would evaluate EXPR1 and try to match it with BINDING. If BINDING matches, then EXPR2 is evaluated with BINDING in scope. If EXPR2 evaluates to true, then STMT3 is run.
If any of the above fails, STMT4 is run.

The advantage of this syntax is that it could, later, if need arises, be extended to an arbitrary sequence of let and expressions in turn. (not trying to include those for now as it's likely better to stay with a conservative syntax -- I was even thinking of enforcing putting parenthesis around EXPR2, but I think the paragraph below handles this quite well too)

However, in order to not introduce unexpected things (like if (let a = 3) && a == 3 || b == 2 which would parse despite operator precedence making || theoretically the highest-level item of the AST -- but not with the proposed syntax), an additional rule should be placed stating that in EXPR2, the top-level node of the AST must be of a precedence stronger than or equal to the one of &&.

Additionally, depending on the current usage among crates, a warning could be put in when doing if let a = b && c, as it'd be parsed as b && c matched into a: this could take the form of a warning put forward when parsing an if let, if the RHS of the match has a top-level AST node of && and is not parenthesized.

With this I think we should have make a run around the potential issues of syntax put forward by this proposal.

The [expr] is [pat] syntax that @petrochenkov has been working on hasn't been RFCed yet,

Overall, I think it'd likely be better to first RFC the [expr] is [pat]

There are some holidays in May, so I hope to write an EXRP is PAT RFC based on the implementation experience.
After "tasting" the feature while porting rustmt to my experimental branch I now really miss it while writing normal Rust code accepted by the standard compiler.

I'm not sure about overloading && in this way. As nice as it is to have, I view if let already as kind of hacky syntax, and overloading && to work on pattern expressions in a really niche way only compounds the problem.

I think is is better if I'm understanding it correctly, though if this supports && I'm still not a fan of declaring and using something in the same header:

if foo is Some(x) && bar is Some(y) && x > y { }

Is perhaps clear, but the semantics are a bit weird. It's much better than if (x = foo()) in C, for certain, but something about allowing && (and ||) to double duty pattern matches as boolean expressions AND bindings which occur in the same loop header, and also have left-to-right scoping is a bit head spinning when I try and think about it.

To be honest, we could use "where" for this:

if x < y where foo is Some(x), bar is Some(y) {

}

Extends to if/else blocks:

if x < y {

} else if x > y {

} else {

} where foo is Some(x), bar is Some(y)

or possibly even allowing different decompositions via if else (rather than forcing the user into match):

if x < y where foo is Some(x), bar is Some(y) {

} else if x > 5 && z < 10 where foo is Some(x), baz is Some(z) {

}
// ...

Another option if you prefer forcing users to declare their variables before use rather than needing to defer name resolution is something like "st" for "such that"

if foo is Some(x), bar is Some(y) st x < y {

}

I feel like these more neatly communicate a separation of ideas as far as declaring bindings vs testing conditions on those bindings. While it's true the binding is itself somewhat a condition, personally I feel they're sufficiently different to warrant syntactic separation as well.

The only major potential drawback I see to this approach over if foo is Some(x) && x > 5 style is losing short circuiting power (if foo is Some(x) && x > 5 && some_expensive_function() is Some(y)), but personally I'm not sure burying expensive calls behind short circuits is the best style anyway.

I agree with @LinearZoetrope that the combination of is && || is weird. What about this:

if (foo, bar) is (Some(x), Some(y)) where x > y { }

The && part in C++ is ; . But I guess Rust doesn't have to be the same though.

I can't say that the is syntax feels particularly appealing, not least of which because it reverses the order of the pattern and the expression to match against it. I prefer the proposal of parenthesizing the let.

Closing this since https://github.com/rust-lang/rfcs/pull/2497 was accepted.

If I may, I think there's been a communication issue here: this issue should have been pinged when the eRFC was open. Now… it does look good, thanks!

Closing this since #2497 was accepted.

While they might sound similar, #2497 actually has no overlap with this issue:

  • #2497 covers chaining multiple if lets together e.g. if let Some(x) = foo && let Some(y) = bar
  • This issue is for adding a _guard_ to a single if let e.g. if let Some(x) = foo && x > 42

Therefore I believe this issue should stay open.

As a side note, we should consider including the change to while let as well and not only if let

@davidrolle #2497 already included if let Some(x) = foo && x > 42 and while let &&. Please check the RFC text in additional to the summary.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

camden-smallwood-zz picture camden-smallwood-zz  Â·  3Comments

p-avital picture p-avital  Â·  3Comments

3442853561 picture 3442853561  Â·  3Comments

steveklabnik picture steveklabnik  Â·  4Comments

marinintim picture marinintim  Â·  3Comments