-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Let-else source expression move behavior #89688
Comments
This definitely seems wrong. The As per reference level explanation in the RFC the construct should desugar into a match, pretty much. And the desugared version does allow for using the matchee in the "otherwise" branch. |
That's my understanding as well: the RHS of the let should be available in the else expression. |
Yes, that's what I'm thinking as well. Though the RFC doesn't seem to have examples showing it. My reasoning was that since I took a look at the code, but I'm not familiar enough with the internals to see where the "place identity" of the expression gets lost. |
I thought I covered this in the RFC, but this is definitely a bug in my (the RFC author’s) view. |
A practical use case of this is logging from the the matchee in an error case. |
The desugaring is not made to a plain if-let-then-else, it is into rust/compiler/rustc_ast_lowering/src/block.rs Line 141 in 1067e2c
Using the dummy causes a move regardless of whether you add the annotation or not. That is the key difference between the let-else and the manual if-let desugarings posted in this thread. If you desugar this according to the current implementation: let Some(inner) = value else {
println!("other: {:?}", value);
return
};
println!("inner: {:?}", inner); You get, in fact: let dummy = value;
if let Some(inner) = dummy {
println!("inner: {:?}", inner);
} else {
// value has moved into _dummy, above
println!("other: {:?}", value);
return
} ... which does not compile. Same goes for when you write I don't really see how you can desugar the type annotation in such a way that the original scrutinee does not get moved. But also:
So I think you could just remove that syntax, desugar without the dummy binding, and it would all work as expected. |
let-else is supposed to be a straightforward extension to let; it should support type annotation just as let does. If we can't support it with a trivial desugaring, then we should support it "natively" as a construct. |
So I kinda though that using Unassigning self for now since I don't see myself working on this for a while, but I may come back to it. |
I have been searching in vain for a use case for type annotations on a refutable let binding, and I found another bug that whatever replacement implementation will have to address too. This will never make much sense as the let [single]: Vec<_> = iter.collect() else {
return;
}; The general case is this, with the same error let [single] = Vec::new() else {
return;
};
(Incidentally there should be a FromIterator for this that doesn't allocate! Like so.) |
The most obvious use case I can think of is try_into. |
Yeah, but I think it might be the only one, and it is not great. The let-else was meant to help you get the type you're decomposing out of the way, but you have to specify that it's fallible operation no less than three times (Ok, Result, try_into) if you want to write the target type explicitly. // explicit handling
let Ok(x): Result<u32, _> = y.try_into() else {
return u32::MAX;
};
// remember we want people to write all that instead of this:
let x = y as u32;
// and the alternative when you have Result return type
let x: u32 = y.try_into()?; I'm with you, but it brings me no joy. |
I'm having a crack at it by adding an I'm new at this, is it weird that typeck ExprUseVisitor walks rust/compiler/rustc_typeck/src/expr_use_visitor.rs Lines 461 to 470 in 9475e60
rust/compiler/rustc_typeck/src/expr_use_visitor.rs Lines 629 to 631 in 9475e60
This just seems suspicious, I am not certain it would fix anything, but it feels wrong. (Edit: I think it's just the name walk_irrefutable_pat that's incorrect. It doesn't do anything differently from walk_arm.) |
Yeah I've fixed this, will PR when I finish the knock-on hir changes in clippy etc |
Implement let-else type annotations natively Tracking issue: #87335 Fixes #89688, fixes #89807, edit: fixes #89960 as well As explained in rust-lang/rust#89688 (comment), the previous desugaring moved the let-else scrutinee into a dummy variable, which meant if you wanted to refer to it again in the else block, it had moved. This introduces a new hir type, ~~`hir::LetExpr`~~ `hir::Let`, which takes over all the fields of `hir::ExprKind::Let(...)` and adds an optional type annotation. The `hir::Let` is then treated like a `hir::Local` when type checking a function body, specifically: * `GatherLocalsVisitor` overrides a new `Visitor::visit_let_expr` and does pretty much exactly what it does for `visit_local`, assigning a local type to the `hir::Let` ~~(they could be deduplicated but they are right next to each other, so at least we know they're the same)~~ * It reuses the code in `check_decl_local` to typecheck the `hir::Let`, simply returning 'bool' for the expression type after doing that. * ~~`FnCtxt::check_expr_let` passes this local type in to `demand_scrutinee_type`, and then imitates check_decl_local's pattern checking~~ * ~~`demand_scrutinee_type` (the blindest change for me, please give this extra scrutiny) uses this local type instead of of creating a new one~~ * ~~Just realised the `check_expr_with_needs` was passing NoExpectation further down, need to pass the type there too. And apparently this Expectation API already exists.~~ Some other misc notes: * ~~Is the clippy code supposed to be autoformatted? I tried not to give huge diffs but maybe some rustfmt changes simply haven't hit it yet.~~ * in `rustc_ast_lowering/src/block.rs`, I noticed some existing `self.alias_attrs()` calls in `LoweringContext::lower_stmts` seem to be copying attributes from the lowered locals/etc to the statements. Is that right? I'm new at this, I don't know.
The move behavior on non-matching of expressions by
feature(let_else)
seems currently inconsistent with behavior of, for example,if let
.An
if-let
construct does not move from the matched expression if the pattern doesn't match:compiles and prints
However,
let-else
seems to unconditionally move from the matched expression:fails to compile:
I'm wondering if this is intentional, as it would seem to make it impossible to use the original unmatched value in an panic message (or diverge otherwise with the value or any value based on it).
Proposed Label: F-let-else
The text was updated successfully, but these errors were encountered: