2

I have two structs, Holder and Held. Holder holds a reference to Held. Held holds an i32:

struct Holder<'a> {
    val: &'a Held,
}

#[derive(Debug)]
struct Held(i32);

I want to create 10 Holders in a Vec<_> named holders. Since Holder takes a reference to Held struct, I also create a Vec<_> named heldvals that will store the Held structs for the scope of main function:

pub fn main() {
    // contains the `Holder`s
    let mut holders = vec![];

    // contains the `Held`s
    let mut heldvals = vec![];

    for i in 0..10 {
        heldvals.push(Held(i));

        holders.push(Holder {
            val: &heldvals.last().unwrap(),
        });
    }
}

When I attempt to compile this program, I get an error:

error[E0502]: cannot borrow `heldvals` as mutable because it is also borrowed as immutable
   |
   |         heldvals.push(Held(i));
   |         ^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
   | 
   |         holders.push(Holder {
   |         ------- immutable borrow later used here
   |             val: &heldvals.last().unwrap(),
   |                   -------- immutable borrow occurs here

As a workaround, I reluctantly decided to use unsafe, which works without any errors. I even implemented the Drop trait to confirm that there is no memory issue.

// ...
impl Drop for Held {
    fn drop(&mut self) {
        dbg!(self);
    }
}

pub fn main() {
    let mut holders = vec![];

    let mut heldvals = vec![];
    let hptr = &mut heldvals as *mut Vec<Held>;

    for i in 0..10 {
        println!("creation");
        unsafe {
            (*hptr).push(Held(i));
        }

        holders.push(Holder {
            val: &heldvals.last().unwrap(),
        });
        println!("replacement");
    }
}

Running the code above gives this (reduced) output:

creation
replacement (10 times)
[src/main.rs:12] self = Held(
    0,
)
... 
[src/main.rs:12] self = Held(
    9,
)

Valgrind shows no memory leaks or issues either:

HEAP SUMMARY:
    in use at exit: 0 bytes in 0 blocks
  total heap usage: 18 allocs, 18 frees, 3,521 bytes allocated

All heap blocks were freed -- no leaks are possible

ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Is there a way to avoid the usage of unsafe? I found out about Vec::reserve(), is that a good idea?

Cannot borrow as mutable because it is also borrowed as immutable and other answers of similar kind are too simplistic and do not explain the relation between loops and borrow errors. Moreover, they do not give a pointer towards an alternative solution.

Usage of reference counters is impossible for me. I want a simple way to hold references until program exits.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
arunanshub
  • 581
  • 5
  • 15
  • 3
    Your program contains memory unsafety. Every time you `push` to a vector, it might reallocate, moving the data around in memory. The pointers / references you create are not guaranteed to be valid. Your lack of Valgrind warnings use pure (bad) luck. – Shepmaster Nov 30 '21 at 18:55
  • 1
    It looks like your question might be answered by the answers of [Cannot borrow as mutable because it is also borrowed as immutable](https://stackoverflow.com/q/47618823/155423). If not, please **[edit]** your question to explain the differences. Otherwise, we can mark this question as already answered. – Shepmaster Nov 30 '21 at 18:58
  • 1
    A common solution is to store indices into the Vec instead of references. Indices won't catch the ire of the borrow checker. – John Kugelman Nov 30 '21 at 19:11
  • @Shepmaster I'll remove the first question since the reason became quite clear to me as I browsed through `Vec<_>`'s documentation. However, I believe that Question 2 and 2.1 are related since [doc suggests that `reserve()` reserves a finite amount of memory](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.reserve), which can avoid reallocation and memory issues. – arunanshub Nov 30 '21 at 19:11
  • If you plan to reserve a fixed amount of memory you'll probably have to take the next step and switch the vector to a slice. `reserve()` is unlikely to appease the borrow checker since reservations are at runtime and the borrow checker runs at compile-time. – John Kugelman Nov 30 '21 at 19:14
  • 1
    If your question has been answered might I suggest opening a new post for your follow-up questions? This one is nicely scoped. It would be a good signpost to the question Shepmaster linked. Adding more questions and neutering the original one will lead to a messier post than just starting fresh. – John Kugelman Nov 30 '21 at 19:18
  • @JohnKugelman Thanks for the suggestion! I have narrowed down my question to retain its uniqueness and limit its breadth. – arunanshub Nov 30 '21 at 19:23
  • 2
    I can't reopen on my own, but the basic thing here is that once you re-change `heldvals` then `holders` is completely invalidated. So if you populate `heldvals` completely, and then iterate through it to populate `holders`, then you're OK. But once you change `heldvals` again, `holders` is invalidated. Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=54b5dfe2dad07e8c813e41ac6870641e - comment out the last two lines, then put them back in to see what happens. – Kevin Anderson Nov 30 '21 at 21:10
  • 1
    @KevinAnderson It is now reopened, in case you wanted to answer – Félix Adriyel Gagnon-Grenier Dec 21 '21 at 01:55

1 Answers1

1

Copying from my reply above:

The basic thing here is that once you re-change heldvals then holders is completely invalidated. So if you populate heldvals completely, and then iterate through it to populate holders, then you're OK. But once you change heldvals again, holders is invalidated

#[derive(Debug)]
struct Holder<'a> {
    val: &'a Held,
}

#[derive(Debug)]
struct Held(i32);

pub fn main() {
    // contains the `Holder`s
    let mut holders = vec![];

    // contains the `Held`s
    let mut heldvals = vec![];

    for i in 0..10 {
        heldvals.push(Held(i));
    }
    for i in 0..10 {
        holders.push(Holder {
            val: &heldvals[i],
        });
    }
    for cur in holders.iter()
    {
        println!("cur: {:?}", cur);
    }
    // invalidate the holders
    heldvals.push(Held(15));
    println!("holders[1]: {:?}", holders[1])
}

That will not compile. The last two lines give this error:

   Compiling playground v0.0.1 (/playground)
error[E0502]: cannot borrow `heldvals` as mutable because it is also borrowed as immutable
  --> src/main.rs:29:5
   |
21 |             val: &heldvals[i],
   |                   -------- immutable borrow occurs here
...
29 |     heldvals.push(Held(15));
   |     ^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
30 |     println!("holders[1]: {:?}", holders[1])
   |                                  ------- immutable borrow later used here

For more information about this error, try `rustc --explain E0502`.

But remove those lines, and it works:

cur: Holder { val: Held(0) }
cur: Holder { val: Held(1) }
cur: Holder { val: Held(2) }
cur: Holder { val: Held(3) }
cur: Holder { val: Held(4) }
cur: Holder { val: Held(5) }
cur: Holder { val: Held(6) }
cur: Holder { val: Held(7) }
cur: Holder { val: Held(8) }
cur: Holder { val: Held(9) }

As above, it's because you have two mutables, and once you've borrowed from the first, you can't alter it until the borrow is "given back". You're essentially "locking" the first vector once you borrow something (or many things) from it. You can't build heldvals and holders at the same time, or alter heldvals after you've borrowed from it.

Kevin Anderson
  • 6,850
  • 4
  • 32
  • 54