Problems with borrow of `*self` and function argument of same struct

How can I solve following example:

struct Foo {
    items: Vec<u8>,
}

impl Foo {

    pub fn add(&mut self, items: &mut Vec<u8>) {
        items.push(1);
    }

    fn test(&mut self) {
        self.add(&mut self.items);
    }
}

fn main() {
    let mut foo = Foo { items: Vec::new() };
    foo.test();
}

The above code won't compile because of the borrow checker. How can I handle this to supply a member function a reference to a parameter which is a member of the same struct?
In my use case function add is called in the same struct, but also in other structs too, with different parameters.

Your add() method won't work when passing &mut self.items to it because you'll have two mutable references to the items: Vec<u8> member. Inside add() you could mutate it via the items parameter directly, or via self.items and this goes against the borrowing rules.

I'm guessing your real add() method is more complicated and mutates other bits of self. One of the more common strategies is to "split the borrow" so that instead of mutably borrowing all of self (in the method call) while also trying to pass some mutably borrowed property, you take mutable borrows to just the fields you need.

For example, say you maintained an internal counter and wanted add() to append that counter to the list of items and increment itself.

struct Foo {
  items: Vec<u8>,
  next: u8,
}

impl Foo {
  fn test(&mut self) {
    add(&mut self.items, &mut self.next);
  }
}

fn add(items: &mut Vec<u8>, value: &mut u8) {
  items.push(value);
  *value += 1;
}

Or if you don't need to pass in &mut self.items as a parameter and are okay with always appending to the member directly.

struct Foo {
  items: Vec<u8>,
  next: u8,
}

impl Foo {
  fn add(&mut self) {
    self.items.push(self.next);
    self.next += 1;
  }
}

In this case, the problem rustc is trying to prevent is Iterator Invalidation. If you try to append part of a Vec<u8> to itself you can run into the issue where the Vec<u8> needs to resize midway through (e.g. because it didn't have enough space) and the reference you took to the items being added now points at garbage.

fn add_first_five_items_to_itself(items: &mut Vec<u8>) {
  let first_five = &items[..5];  // gets a pointer directly to the u8's
  items.extend(first_five);      // may trigger a resize, which
                                 // would leave first_five pointing at garbage
}
2 Likes