Search code examples
rustborrow-checkerownershipborrowing

How to initialize an array and save references to its items?


let mut u:[usize;3] = [0;3];
let mut v = vec![];
for i in 0..3 {
    u[i] = i;
    let e = &(u[i]);
    v.push(e);
}
error[E0506]: cannot assign to `u[_]` because it is borrowed
 --> src/lib.rs:5:9
  |
5 |         u[i] = i;
  |         ^^^^^^^^ assignment to borrowed `u[_]` occurs here
6 |         let e = &(u[i]);
  |                 ------- borrow of `u[_]` occurs here
7 |         v.push(e);
  |         - borrow later used here

(Playground)

I understand the compiler is saving me from myself, but I really want this behavior, and I don't want to force it through with unsafe as I'm sure to screw up. What are my options?

I have a feeling it has something to do with lifetimes which I have yet to comprehend. Perhaps it's something to do with having two owners for the same data? I wouldn't think this had to be an issue as long as u outlived v.

How can I recover from this? Or is it just plain disallowed due to multiple owners and thus I need to rethink my approach?

I'm going through this trouble because I need to copy a type that only can be cloned but I'll have to ask another question for that.


Solution

  • Iterators come to your rescue!

    Since the compiler can't reason with the code in your loop, you need to put restrictions in, in the form of type system and lifetime requirements. This is accomplished through iterators:

    let mut u: [usize; 3] = [0; 3];
    let mut v: Vec<&usize> = vec![];
    u
        .iter_mut()
        .enumerate()
        .for_each(|(idx, val)| {
            *val = idx;
            v.push(&*val);
        });
    

    Playground.

    As per request; here's what this does:

    • u.iter_mut() comes from the fact that an array coerces to a slice, and slices have an iter_mut function. This iterator yields a &mut T.

    • .enumerate() takes an iterator, and slaps on an index. Your iterator item transforms as follows: T goes to (usize, T).

    • .for_each(f) completely empties the iterator and runs a function for each element (f is the function). f in this case must have the call signature fn(value: (usize, &mut T)) -> ().

    • We pass in a closure into the for_each call, and that's important: the closure can mutably capture v. This permits modification of v, and therefore we can say v.push in the closure.

    As to why this works and yours doesn't:

    • You have a plain for loop. The for loop can do anything, and the compiler won't put in the effort to validate what you're doing. It will just make sure what you're doing isn't illegal according to the compiler, and will be conservative about that.

    • This, on the other hand, uses the semantics of a closure to guarantee that you can't modify u in any other way than through the element you're seeing right this instant. The compiler sees the references to each element of u are unique since the original reference to u was unique, and so it has no issue with this. Then, the lifetime of the borrows (or references, same term) in v is the same lifetime as that of u, and so it concludes that v borrows from u, in an elegant fashion.

    • This would not have been easy through the loop, since the compiler would literally have to trace your code call by call, line by line, to determine this otherwise. It would have been a pickle. And a pickle it is, so it just spits an error at you since it'd be too difficult to prove in more complex cases.