-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Improve borrow error when assigning borrowed local value to "argument by mutable reference" #99430
Comments
I dislike the suggested error message- |
This is an odd one. I used to think that fn f(p: &mut i32) {
let mut p = p; // <-- suppresses error!
let mut number = 111;
p = &mut number;
*p = 2;
println!("{}", *p);
} I'm not familiar with rustc internals so I don't know what information is known by this pass, but is it possible to distinguish this specific case from the more general case and suggest introducing a let-binding? i.e. something like this:
... or this
|
I heavily dislike this phrasing, and would go as far as to say it's incorrect. Now to help demonstrate, here's an example that I think this *would* be applicable for: fn f(p: &mut &mut i32) {
let mut number = 111;
*p = &mut number;
**p = 2;
println!("{}", **p);
} (Note the change from Going back to the phrasing:
I would argue this implies the reference (not the underlying data) is owned by the caller. Which, isn't really the case, as it's actually pass-by-move-ing the reference, so the caller doesn't actually own the reference itself anymore. Brief intermission: the distinction I'm making is both pedantic and dependent on (a) my mental model and (b) my interpretation of words like "own". So really, whether or not I'm correct comes down to semantics and wholly respect disagreement, but everything will be phrased as if it is fact for the sake of not bogging this down with uncertain phrasing. So to indicate what I feel is actually happening, I think it's important to desugar what's going on, especially as I think Rust could very well allow the code you posted (more on this in a minute). This means that the explanation being in terms of "this is wrong, it doesn't live long enough" is potentially suboptimal, as the reason the code is rejected is more arbitrary than that. So desugared: fn f<'a>(mut p: &'a mut i32) {
let mut number = 111;
p = &mut number;
*p = 2;
println!("{}", *p);
} Now the issue here is not "the reference outlives So maybe the explanation should include the desugaring? I am not sure how to explain it otherwise. |
I think @ryanavella's suggestion is not a bad one (honestly not sure I like that's it's necessary though. I'm curious if lifetimes should be coercible to the minimum when not observable by the caller?), although I would add some context explaining why this fixes it:
(phrasing subject to overhaul, my only nit was "this suggestion is too magical and unintuitive without the 'why'") |
Note that you can compile code like the OP wrote when there's a deeper pattern involved -- see #86989. fn f((_i, mut p): (usize, &mut i32)) {
let mut number = 111;
p = &mut number;
*p = 2;
println!("{}", *p);
} But still, if the intention was to change the caller's reference to the local, that will never work. |
@cuviper That is definitely interesting, even a 1-tuple is enough of a pattern to suppress the error: fn f((mut p,): (&mut i32,)) {
let mut number = 111;
p = &mut number;
*p = 2;
println!("{}", *p);
} It isn't clear to me which should be the "correct behavior" here. I can see an argument that the error is spurious because there is nothing inherently unsound here. Plus the error message is confusing to newcomers. But is it possible that some users could be relying on this error to enforce a type-level invariant? If so, I wouldn't know how to construct an example. All of the examples I can think of can be easily bypassed by shadow-binding, as @BoxyUwU first suggested. |
wow! great link @cuviper, very interesting behavior. hopefully the non-nested bindings are loosened to act like the nested ones? |
It is very confusing to new comers. But so is the whole situation. It seems quite a lot of people seem to assume the problem is that That is the only thing you can do by declaring it mut. It makes me think that the caller either mean to rebind with |
@ryanavella Subpatterns are enough. fn f(mut p @ _: &mut i32) {
let mut number = 111;
p = &mut number;
*p = 2;
println!("{}", *p);
} |
I managed to get the exact spans I originally wanted, and I tested a few different cases: This is the case that (I'm guessing) I was really interested in, but it is different from the one in the ticket. I believe that the proposed wording was accurate for this case.
This is the case in the report that people have pointed out should not have been rejected in the first place. I believe that this is a separate issue, but it also gets the new span labels :-/
This is a variation where the argument is not mutable and assigned to. No changes, other than the suggestion pointing you maybe in the wrong direction, but the three errors being emitted might have enough context for people to make the right choice. I'm not sure.
|
Current output:
|
Related: fn main() {
let x = 0;
let mut y = &x;
let mut z = &y;
y = &1;
// rm `let` to get E0716
let z = &&x; // why does shadowing magically fix it?
println!("{z}");
} Playground: 1.79.0-nightly (2024-04-21 fb89862) The docs don't explain anything about that: |
Given
we currently emit
but it could be more informative:
The text was updated successfully, but these errors were encountered: