-
Notifications
You must be signed in to change notification settings - Fork 13.6k
Description
This is my first issue for the Rust project. Apologies up front if it is somehow unclear. I'd be happy to provide any additional information you need.
DESCRIPTION
The issue I'm running into is this. Consider the following code:
collection.get_mut(collection.get(0).unwrap().index).unwrap().index = 1;
The inner call to get
causes an immutable borrow on collection
. We then obtain a index from the return value, and use that for the outer call to get_mut
. This causes a mutable borrow on collection
.
The above code compiles just fine. My assumption is that because index
implements Copy
, NLL allow the immutable borrow on collection
to be dropped as soon as we have a copy of index
, so we can borrow it mutably again after that.
So far, so good. However, now consider the following code:
collection[collection[0].index].index = 1
;
EXPECTED BEHAVIOR
This should be equivalent to the earlier code. In fact, I've implemented the Index
trait to forward to get(index).unwrap()
.
ACTUAL BEHAVIOR
This time, however, the compiler complains:
error[E0502]: cannot borrow
collection as immutable because it is also borrowed as mutable
For more context, here is a playground link that illustrates the problem:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=21d19fde2c8c58d4859bfdefa3b7720a
Activity
ejpbruel2 commentedon Feb 13, 2019
Cc'ing @nikomatsakis as I've been told he'd be particulary interested in issues like this one.
hellow554 commentedon Feb 13, 2019
Hmm, you're right. This should be possible with NLL, e.g. using
does work.
pnkfelix commentedon Feb 13, 2019
Hmm I wonder if this is due to some weakness in how we try to limit two-phase borrows to just auto-refs on method calls and indexing. (This was implemented as part fo addressing #48431)
In particular, my hypothesis is that whatever method the compiler is using to pattern-match the code to see if we should attempt to apply two-phase borrows, it is probably getting foiled by the
.index
projection in the code above.Generalizing two-phase borrow in some manner is generally tracked in #49434, though I imagine we might attempt to land a more targetted fix for this particular issue sooner than we attempt a more complete generalization.
pnkfelix commentedon Feb 27, 2019
triage: P-medium.
I am assuming that we will consider widening the semantics of 2-phase borrows in the future in a manner that might allow this code to pass. But such a change in the static semantics is not as high priority as other tasks for the NLL team.
mbrubeck commentedon Apr 1, 2021
Related thread on the internals forum
In that thread, SNCPlay42 notes that this already works on types that implement the
IndexMut
trait directly (like[T]
), but not on types thatDerefMut
to an indexable type (likeVec<T>
). The same is true for method calls, which similarly don't work when an implicitDerefMut
is required. So this isn't actually a difference between indexing and function syntax.pnkfelix commentedon Dec 14, 2022
(Sorry for the length of this comment; it is transcribed from #74319 which I where I had mistakenly posted it initially.)
After reading this and related issues, and re-reading:
rust/compiler/rustc_middle/src/ty/adjustment.rs
Lines 135 to 151 in 96859db
Here is a summary of my understanding of the situation:
v[IDX] = E
(whereIDX
is an expression that calls a&self
-method onv
) is rejected, while its desugared form is accepted by two-phase borrowsv[IDX] = E
doesn't desugar to*v.index_mut(IDX) = E
(despite what its documentation says); it actually desugars to*IndexMut::index_mut(&mut v, IDX) = E
, and the latter is rejected by the borrow-checker today, regardless of two-phase borrows.Foo::some_method(&mut x, ...)
syntax are one class of desugarings supported by two-phase borrows, but clearlyIndexMut
is not placed in that class today.DerefMut
;IndexMut
on its own, without an interveningDerefMut
, can be demonstrated to work.So, what is currently blocking support for
v[IDX] = E
(whereIDX
calls&self
method onv
)...?foo(&mut v, expr_using_v)
. That is what is tracked by Tracking issue for generalized two-phase borrows #49434IndexMut
+DerefMut
as a special case.I've already expressed concerns about trying to deliver fully generalized two-phase borrows without a proof of soundness; you can see those comments on #49434.
(If you want to see an example of why we are trying to be conservative here, maybe take a look at #56254, which led us to ratchet back some of two-phase borrows initial expressiveness because we weren't confident that it was compatible with the stacked-borrows model from that time.)
A lot has happened since those discussions; e.g. models have evolved. But we still don't have any proof of soundness of generalized two-phase borrows, as far as I know.
(Having said all that, you all might be able to talk me into treating
IndexMut
as a special case... at the very least, we could experiment with such a change.)vec.swap(0, vec.len() - 1)
, whilevec.push(vec.len())
works. #74319v[v.len()-1]
not accepted by borrow checker #105678Danvil commentedon Dec 14, 2022
@pnkfelix I would try to talk you into at least supporting
IndexMut
as a special case for now.Below is a related real-life use case:
workingjubilee commentedon Feb 17, 2023
@Danvil It would be more direct to implement
IndexMut
on the wrapping type you desire, like so, and it compiles (Playground):Danvil commentedon Jan 19, 2024
@workingjubilee The issue was that I can't define the helper function
idx
and use it in the&mut
case. Your example works because you got rid of that function and duplicated the code. In this example the function is small and simple, but in many cases it would lead to unnecessary and erroneous code duplication.