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

if let && bool #2411

Closed
Ekleog opened this issue Apr 20, 2018 · 13 comments
Closed

if let && bool #2411

Ekleog opened this issue Apr 20, 2018 · 13 comments
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.

Comments

@Ekleog
Copy link

Ekleog commented Apr 20, 2018

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 #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 Support && in if let expressions #929)

What do you think about this idea?

@sinkuu
Copy link
Contributor

sinkuu commented Apr 21, 2018

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,
}

@Centril Centril added the T-lang Relevant to the language team, which will review and decide on the RFC. label Apr 21, 2018
@Ekleog
Copy link
Author

Ekleog commented Apr 21, 2018

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 #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?

@Centril
Copy link
Contributor

Centril commented Apr 21, 2018

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.

@Ekleog
Copy link
Author

Ekleog commented Apr 21, 2018

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.

@petrochenkov
Copy link
Contributor

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.

@UserAB1236872
Copy link

UserAB1236872 commented Apr 28, 2018

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.

@F001
Copy link
Contributor

F001 commented May 3, 2018

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 { }

@crlf0710
Copy link
Member

crlf0710 commented May 3, 2018

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

@joshtriplett
Copy link
Member

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.

@Centril
Copy link
Contributor

Centril commented Oct 7, 2018

Closing this since #2497 was accepted.

@Centril Centril closed this as completed Oct 7, 2018
@Ekleog
Copy link
Author

Ekleog commented Oct 7, 2018

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!

@davidrolle
Copy link

Closing this since #2497 was accepted.

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

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

@kennytm
Copy link
Member

kennytm commented Jan 16, 2020

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

No branches or pull requests

10 participants