Open Centril opened 5 years ago
I'm working on implementing this in a 3+ PR step fashion based on discussions with @oli-obk.
for the record, the discussed step list is
if
from HIR
and make lowering emit a match
if let
from AST
and instead have a let
expressionI can't imagine 3 would be a particularly large step (enough to merit splitting), but 3 steps sounds fair enough.
@Centril I'm really jazzed about this feature, lmk if there is any work I can help with.
@estk :) The work is pretty non-parallelizable at the moment; However, once https://github.com/rust-lang/rust/pull/60861 lands you may want to take a look at https://github.com/rust-lang/rust/issues/60336.
Question worth discussing: This introduces let
as something like an expression in the AST.
Do we plan to make it a true expression? As in, will $e:expr
match let Pat(..) = foo
in macros? This does not mean that it will work everywhere (as @centril correctly points out, $t:ty
matches impl Trait
, but it's only allowed in certain positions in the AST), it just means that macros will be able to match on it. They may error further down the pipeline if it gets used outside of an if
.
This is technically a breaking behavior change; a macro with a fallthrough to let $p:pat = $e:expr
will suddenly start behaving differently.
@Manishearth I thought that you can't probe whether something parses as an AST fragment in macro_rules
macros, only tokens (this includes $i:ident
, $l:lit
, etc.).
That is, I expected the first expr
/pat
/stmt
/block
/item
/etc. that is reached to be final.
But I'm wrong, and this is either behavior introduced in the past couple years, or I was just confused.
this is either behavior introduced in the past couple years
This is https://github.com/rust-lang/rust/pull/42913 if I'm not mistaken, almost 2 years ago. One token lookahead is used to reject alternatives that "obviously don't fit".
(not sure this is the right place for this, but I don't think we have anything else opened for "pattern matching expressions" discussion).
I've just noticed that Java is getting pattern matching along the lines proposed in https://github.com/rust-lang/rfcs/issues/929#issuecomment-285602496. That is, control-flow sensitive scoping and patterns on the right are now almost Java-level proven and trusted language design choices.
any actual syntax for this in nightly rust? just cannot type it to work.
! Compiling of exercises/conversions/from_into.rs failed! Please try again. Here's the output:
error: `let` expressions are not supported here
--> exercises/conversions/from_into.rs:44:33
|
44 | if (parts.len() == 2 && let Some(n) = name && let Some(Ok(a))= age) {
| ^^^^^^^^^^^^^^^^^^
|
= note: only supported directly in conditions of `if`- and `while`-expressions
= note: as well as when nested within `&&` and parenthesis in those conditions
not nice
if parts.len() == 2 { if let Some(n) = name { if let Some(Ok(a)) = age {
return Person{name: n.to_owned().to_string(), age : a,};
}}}
Any progress updates? Although I love rust this is by far the jankiest part of the language
I am trying to tackle this feature one bit at a time in my spare time. No promises for a concrete release date though
Currently if I had to match against two different Option<T>
s I'd have to:
if let (Some(smth), Some(another_smth)) = (smth_option(), another_smth_option()) {
/* more code */
}
IMHO this works, but not an ideal way to me. I like the idea of this RFC. Looking forward to this in Rust! 👍
Just to make it official
@rustbot claim
@c410-f3r How goes the development on this? (No rush, just checking in.)
@joshtriplett hir::ExprKind::If
(#79328) was merged and hir::ExprKind::Let
(#80357) will probably be merged until the end of this month. After that, I will start to actually implement the feature.
Everything so far was very challenging/laborious and I am not sure if the worst is yet to come. Personally, my hopes for a solid implementation and stabilization are on the Q3~Q4 of this year.
One thing that I would like to emphasize is the help that @matthewjasper has been given me. Without him, I wouldn't be able to do half of what has already been done. Thanks @matthewjasper!
It was not clear to me reading the RFC (https://github.com/rust-lang/rfcs/blob/master/text/2497-if-let-chains.md) whether the proposed precedence rules (=
having higher precedence than &&
) applies to regular let
statements too, or just in an if
or while
context.
E.g., will
let boolean_value = returns_boolean() && also_returns_boolean();
be affected and now require parentheses to compile correctly? I would assume yes...
To be precise, the changes in this RFC entail that || has the lowest precedence at the top level of if STUFF { .. }. The operator && has then the 2nd lowest precedence and binds more tightly than ||. If the user wants to disambiguate, they can write (EXPR && EXPR) or { EXPR && EXPR } explicitly. The same applies to while expressions.
It does mention if
and while
explicitly.
it's been 3 years, what happened to this RFC 😢
I wrote a RFC draft about let expression. I like to hear comments of people involved here before submitting it officially. You can reach me in zulip or issues of my RFC fork (this is better because it is public)
Now I submitted it in this PR. Thanks for your work on if let chain
and I hope this RFC is a positive step in this direction and for the rust in general.
I opened rust-dev-tools/fmt-rfcs#169 to discuss a formatting question.
Why ||
is forbidden in let chains? I didn't find anything about it in RFC
Why
||
is forbidden in let chains? I didn't find anything about it in RFC
"Not supported" is probably a better term
A stabilization attempt is available at https://github.com/rust-lang/rust/pull/94927
The 1.60 stable compiler lead me here for a condition which I, intuitively, wrote like this (a):
if let Some(x) = x && let Some(y) = f(x) {
(1)
} else {
(2)
}
... it said error[E0658]: 'let' expressions in this position are unstable
It seems the only alternative, by now, is to write it like this (b):
if let Some(x) = x {
if let Some(y) = f(x) {
(1)
} else {
(2)
}
} else {
(2)
}
not sure about the full process of going forward with this tracking issue... but, after 4 years it seems the effort is pretty much stalled... :(
I just want to vote for this to be added to the language, as the (b) solution leads to poor code...
... or... am I doing it wrong, somehow?
@zertyz there is a stabilization attempt @ https://github.com/rust-lang/rust/pull/94927 that targets Rust 1.62 (see the comment just before yours), so I wouldn't call this "stalled".
Does this expression work in rust 1.62
if let Some(email) = data.user.email && !email.is_empty() {
q = q.filter(users::email.eq(email));
}
I now get an error in 1.60
error[E0658]: `let` expressions in this position are unstable
@januwA No, the stabilization PR #94927 has not been merged yet, and since 1.61 will become stable and 1.62 beta very soon, it doesn't seem likely for this feature to become stable in 1.62.
~2 years, 4 months, 3 weeks and 1 day of long nights, obstacles and headaches.~
~Hope stabilization won't be reverted but regardless, thanks to everyone who helped make this feature a reality.~
EDIT: Reverted in https://github.com/rust-lang/rust/pull/100538
~2 years, 4 months, 3 weeks and 1 day of long nights, obstacles and headaches.~
~Hope stabilization won't be reverted but regardless, thanks to everyone who helped make this feature a reality.~
EDIT: Reverted in #100538
Celebrated too soon :(
Reopening this tracking issue, since it's not currently stable.
I'd also like to flag https://github.com/rust-lang/rust/pull/94927#issuecomment-1165156328 which leads me to believe that perhaps stabilization was actually premature for that reason as well? It's not clear to me that we ever had a follow-up to #97295 which actually adjusted our restrictions further, and this error leads me to believe that let expressions would have been stable in more places than I expected us to want. @c410-f3r -- can you elaborate on whether there was a follow-up to #97295 that resolved @compiler-errors concern?
Maybe that was https://github.com/rust-lang/rust/pull/98633, but given the error I mentioned in that random MIR test it seems like it may have been (still) incomplete?
issue-92893.stderr
now correctly denies undesired let
expressions in the parser as well as other let
locations like if let Some(elem) = _opt && [1, 2, 3][let _ = ()] = 1
. Therefore, it seems that everything related to https://github.com/rust-lang/rust/pull/94927#issuecomment-1099605024 is fixed.
@Mark-Simulacrum Can you close https://github.com/rust-lang/rust/issues/60707 and https://github.com/rust-lang/rust/issues/60571? They are no longer relevant.
Ah, I missed that we're emitting a "spurious" feature gate error even though the syntax itself is denied in a separate error in that case. Seems OK.
Edit: Oh man I feel silly. Had an extra if
.
I just tried writing if <BOOL> && if let ...
and it fails pretty terribly. Shouldn't this be supported? If not, there should at least be better diagnostics.
fn foo(b: bool, x: Option<u32>) {
if b && if let Some(x) = x {}
}
error: expected `{`, found `}`
--> src/lib.rs:3:1
|
3 | }
| ^ expected `{`
|
note: the `if` expression is missing a block after this condition
--> src/lib.rs:2:8
|
2 | if b && if let Some(x) = x {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
maybe you should try
fn foo(b: bool, x: Option<u32>) {
if b && let Some(x) = x {}
}
@camsteffen I think it'd be worth improving the diagnostics for that case; perhaps we could detect it and give a rustfix-able suggestion for "delete the if
"?
Yes I was thinking that. Seems tricky though since the parser will be in a nested if expression.
This isn't specific to if let
: if true && if true {false}
gives the same error.
It would probably have been nice to disallow this long ago by requiring parens for any nested block expression, as is done for struct expressions, but that ship is long sailed. Definitely should be considered for improved diagnostics and maybe a lint, but it shouldn't affect this RFC.
What is the status on this? Is it ready for a FCP?
@lunabunn According to https://github.com/rust-lang/rust-clippy/issues/9354, the feature was once stabilized, but reverted due to the issue https://github.com/rust-lang/rust/issues/100513. However the latter has been closed, so I guess we should be back on the right track now.
There were a few ICEs related to this feature, including the following I could find.
After the remaining open issues are closed, another FCP may be possible (hopefully ready for 1.65).
Now with #103034 merged, only #99938 is open of your list @Lee-Janggun , which should be fairly easy to close.
I've also filed:
It should ideally be resolved before stabilization. Also, while #100513 is closed, it should probably be reopened because the drop order isn't perfect yet (and there is no lang team approval what a perfect drop order even is).
What are the plans to stabilize it. Is there any chance it gets into 1.66?
Considering 1.66 beta branched on Friday, there is zero chance it'll land then.
As @jhpratt pointed out, it won't stabilize for 1.66 now.
The implementation state has recently been greatly improved by #103034 and #102998. There has also been lang team feedback on the open questions about the drop order.
There is one weird quirk remaining, which is around bool expressions. My PR #103293 tries to address it. The quirk also occurs in non-let if chains, it would still be great to have it fixed before let chains stabilize as it makes the bool conditionals interact with the let
conditionals, which they otherwise don't do: the bool conditionals drop in a totally different pattern from the let
conditionals, and at a totally different time. This separation makes sense as bool conditionals don't leave behind any bindings while let conditionals do. But with the && twist present, adding a let _ = () &&
conditional in front of a bunch of bool conditionals changes the drop order within the group of bool conditionals. That seems worse to me than what non-let if chains are doing.
There is still one big-ish bug in #103476. We might settle on it being an acceptable quirk or even, like #103784, expected behaviour, but ideally it would be investigated further before let chains stabilize.
Outside of that I don't know of any blockers.
As a small new years update on the let-chains feature. Some new developments:
if_chain
crate. Then, if it's open source, you can just put a comment into the dedicated issue, or alternatively you can run the if-to-let-chain tool on your code base yourself, and report any ICEs or lifetime bugs you encounter. I've for example done a migration of clippy with a 9k line diff to find more ICEs like the one above, but I couldn't find any.I am not sure it has been discussed before, and I know it's a syntax which shouldn't be used, but isn't that a bit confusing?
let condition = false;
let some_other_condition = false;
if let false = condition && some_other_condition {
println!("step 1"); // Does not enter here
}
if let false = (condition && some_other_condition) {
println!("step 2"); // Enters here
}
The first notation is ambiguous, and maybe not everyone expect the precedence to go that way. At least, there should be some kind of warning, like “You better write it that way: if !condition && some_other_condition { … }
if that's what you meant”. That warning may even be shown whenever one pattern matches a bool
, regardless of the context.
Wouldn't that be the same as the following:
let condition = false;
let some_other_condition = false;
let a = condition && some_other_condition == false;
let b = (condition && some_other_condition) == false;
println!("{a}, {b}");
Interesting. The above comments are saying that the =
operator in an if let pat = expr
can be thought of as similar to ==
in terms of precedence level, not like =
in assignment statements. That is surprising to me.
I suspect that it was designed this way since it's very common to write code that looks like:
if let Some(x) = foo() && bar(x) {
If the precedence were the other way, the above code would require parentheses.
The relevant section of the RFC is here. It also came up in this thread https://github.com/rust-lang/rust/issues/53667#issuecomment-860336898.
This is a tracking issue for the eRFC "if- and while-let-chains, take 2" (rust-lang/rfcs#2497). For the tracking issue for the immediate edition changes, see #53668.
Steps:
Unresolved questions:
let
as a boolean expression, and possibilities ofis
syntax.if let
s insidematch
armsCollected issues:
Implementation history:
Unresolved problems