6

I know the general answer — You can only borrow mutably once or immutably many times, but not both. I want to know why this specific case is considered simultaneous borrowing.

I have the following code:

fn main() {
    let mut v = vec![1, 2, 3, 4, 5];
    let n = 3;
    // checks on n and v.len() and whatever else...
    let mut s = v[..n].to_vec();
    for i in 0..n {
        v[i + v.len() - n] = s[1];
    }
}

which produces the following error under 1.36.0:

error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable
 --> src/main.rs:7:15
  |
7 |         v[i + v.len() - n] = s[1];
  |         ------^-----------
  |         |     |
  |         |     immutable borrow occurs here
  |         mutable borrow occurs here
  |         mutable borrow later used here

It seems that there is no way for the write to v[x] to happen until x is computed, by which time the immutable borrow will be complete. Since the ordering here is completely in series, why doesn't the compiler recognize the dependency and treat these as non-overlapping borrows? Put another way, is there any scenario where this could lead to an actual problem?

Marouane Fazouane suggested concurrency as a possibility, but I don't think this is the case. If there were another thread with a (presumably) mutable reference, it would be a violation to then call v.len(), or to start v[...]. Here, the compiler knows everything that's happening to v — it's a local definition with no other calls. For me, the question is why is this simultaneous borrowing when there's no way for v[] to happen until len() returns. It's akin to v.mutable_call(v.immutable_call());

Incidentally, an earlier version of the compiler (1.28) gave an error that indicated the close bracket as the end of the mutable borrow, so it seemed order is based on the source order, and since the source has the two intermingled, they could be considered overlapping. If so, surely the compiler could improve this...right?

This seems closely related to Why is there a borrow error when no borrowing overlap is occurring?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
jspencer
  • 522
  • 4
  • 14
  • Maybe because of concurrency ? I would guess that v.len() wouldn't play nice when another thread is changing v. – Marouane Fazouane Aug 08 '19 at 06:07
  • You could alter the length of the vector in that for loop/statement and then what would `v.len()` give you? The old length? The new one? This violates Rust borrowing rules which means either one exclusive (mut) reference or multiple shared (immutable) references. Not both – hellow Aug 08 '19 at 06:07
  • 1
    This is unrelated to your question, but `s[i] = v[i];` will panic at runtime since array access is dependent on the *length* of the vector, not its capacity. A better way to express what you have would be `let s = v[..n].to_vec();` (of course you'll want to make sure n < v.len() to avoid a panic here). – SCappella Aug 08 '19 at 06:19
  • @hellow, if I pull the index calc out to a let statement just above the access, the compiler would be fine with that, but it's subject to the same issue you raise. That issue is one of running out of bounds, which the borrow checker doesn't make any guarantees about. – jspencer Aug 08 '19 at 06:19
  • @SCappella Very good points, updated. Though, as you said, it was not the focus of the question, I did not see the panic coming ('cause I couldn't run it!) Thanks for the catch. – jspencer Aug 08 '19 at 06:23
  • yes but you simply never need to do that in real Rust code. – Stargateur Aug 08 '19 at 16:01
  • @Stargateur Are you saying you never need to index based on the length of the vector? – jspencer Aug 08 '19 at 16:08
  • @jspencer because in rust we would do [this](https://play.integer32.com/?version=stable&mode=debug&edition=2018&gist=dde54bb585a42d387826ed32a85125de) or use iterator, depend on the case. We don't use index expect for few cases. (the exemple does not match your case because I don't understand what you want to do with this strange loop) – Stargateur Aug 08 '19 at 16:19
  • See also [Cannot borrow as immutable because it is also borrowed as mutable in function arguments](https://stackoverflow.com/q/41187296/155423) – Shepmaster Aug 08 '19 at 16:25
  • @Stargteur Thanks for that alternative. Even so, "simply never need to do this" seems an overly strong statement. The original code was from a vector rotation--move n elements from the front circularly to the back. You can imagine in-place transformations reading from one part of the vector and writing to another, in which case an iterator only covers one of those indexing ops. Imagine s[i] here as a different indexing of v also. Granted this is very C-in-Rust-y, and there's always _another_ way, but the question is "why should this way be disallowed?" – jspencer Aug 08 '19 at 16:44
  • @jspencer The thing is Rust don't disallow it, just mutate thing is not something so easy in Rust, still much better than Ocaml :p. But this kind of thing is often build one time in core std or in a crate and the user never have to deal with some unpractical thing in Rust. Well, that the goal at least. If you want to rotate you better look at https://doc.rust-lang.org/std/primitive.slice.html#method.rotate_left. (also, Rust and llvm is better to optimize iterator than index obscure operation)(Instead of describe Y problem that you have trying to solve X, ask directly X and say you try Y) – Stargateur Aug 08 '19 at 23:05

1 Answers1

6

If so, surely the compiler could improve this...right?

Indeed, NLL intentionally started conservatively, as explained in #494341.

Extracting the temporary allows it to compile:

fn main() {
    let mut v = vec![1, 2, 3, 4, 5];
    let n = 3;
    // checks on n and v.len() and whatever else...
    let s = v[..n].to_vec();
    for i in 0..n {
        let index = i + v.len() - n;
        v[index] = s[1];
    }
}

This makes it clear that the issue is strictly one of not computing the index before attempting to use it.

Since it is not possible to start the call to IndexMut<Idx>::index_mut(&mut self, index: Idx) before computing the Idx, there is no reason to start the mutable borrow of v before computing the index.

1 Courtesy of trentcl.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • 1
    _Since it is not possible to start the call to IndexMut::index_mut(&mut self, index: Idx) before computing the Idx_ it works with this way btw `*v.index_mut(i + v.len() - n) = s[1]` , [Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=479383c87c75df47c5ee3788ab08446c) – Ömer Erden Aug 08 '19 at 06:17
  • Right. I guess I'm wondering why the compiler doesn't do this first and then run the borrow checker. – jspencer Aug 08 '19 at 06:17
  • @ÖmerErden: That's interesting! – Matthieu M. Aug 08 '19 at 06:36
  • @jspencer [Issue #49434](https://github.com/rust-lang/rust/issues/49434) relates to this: "Note this does /not/ include `IndexMut` operations at this time". – trent Aug 08 '19 at 12:01