How to think about ownership and multiple mutable references

I feel like I keep running into structural issues with ownership and mutability in my code, and a good example is shown below:

pub struct Person {
    pub name: String,
    pub name_of_spouse: Option<String>,
}

pub struct City {
    pub citizens: Vec<Person>,
}

impl City {
    pub fn marry(p1: &mut Person, p2: &mut Person) {
        p1.name_of_spouse = Some(p2.name.clone());
        p2.name_of_spouse = Some(p1.name.clone());
    }
    
    pub fn marry_first_w_second(&mut self) {
        City::marry(&mut self.citizens[0], &mut self.citizens[1]);
    }
}

This code doesn't compile because City::marry_first causes the city to be borrowed mutably twice. I'd love to hear any suggestions about how I might make this logic work!

Also, feel free to use this code as a specific example, but I'm more interested in learning how I'm supposed to rethink my system logic to handle objects that mutate each other. It feels like this problem disappears in e.g. C#.

Given the way you've modeled the world, I'd say change the signature of City::marry() to accept indices of type usize (or better yet, a newtyped Index type backed by a usize) rather than mutable borrows.

impl City {
    pub fn marry(p1: usize, p2: usize) {
        self.citizens[p1].name_of_spouse = Some(self.citizens[p2].name.clone());
    
 self.citizens[p2].name_of_spouse = Some(self.citizens[p1].name.clone());
    }
    
    pub fn marry_first_w_second(&mut self) {
        City::marry(0, 1);
    }
} 

Im on my phone so I haven't implemented the newtype or verified that this compiles, but should make the idea clear:
The things you exchange across method boundaries shouldn't be mutable borrows, because as you've seen that leads to borrowck issues. Instead, if you exchange indices, the borrowck problem goes away.

The usual trick to get mutable references to different items of a collection simultaneously is called "borrow splitting", and you can use either the split_at_mut() method of slices or pattern matching to perform it:

    pub fn marry_first_w_second(&mut self) {
        if let [first, second, ..] = self.citizens.as_mut_slice() {
            City::marry(first, second);
        }
    }

Playground

4 Likes

For this particular example, you can use pattern matching to create the multiple (non-overlapping) exclusive references into the Vec at the same time:

        if let [first, second, ..] = &mut self.citizens[..] {
            City::marry(first, second);
        }

As for why it was a problem, indexing requires a &mut to all of Self, which in this case is a Vec<Person>. That means exclusive access to all of Self, each time you use IndexMut. So you can only use indexing to hold on to one exclusive borrow of the contents at a time. Rust can track exclusive references to distinct fields of a struct within a function, but not distinct index accesses, except via slice pattern matching. [1]

Another approach would be to split a borrow of &mut [Person] using split_at_mut.


  1. And that only works because slice indexing is built-in and doesn't use the index_mut signature. ↩ī¸Ž

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.