rustreferenceborrow-checkerborrowing

Can I mutate a vector with a borrowed element?


I'm attempting to store a reference to an element of a mutable vector to use later. However, once I mutate the vector, I can no longer use the stored reference. I understand that this is because borrowing reference to the element also requires borrowing a reference to the vector itself. Therefore, the vector cannot be modified, because that would require borrowing a mutable reference, which is disallowed when another reference to the vector is already borrowed.

Here's a simple example

struct Person {
    name: String,
}

fn main() {
    // Create a mutable vector
    let mut people: Vec<Person> = ["Joe", "Shavawn", "Katie"]
        .iter()
        .map(|&s| Person {
            name: s.to_string(),
        })
        .collect();

    // Borrow a reference to an element
    let person_ref = &people[0];

    // Mutate the vector
    let new_person = Person {
        name: "Tim".to_string(),
    };
    people.push(new_person);

    // Attempt to use the borrowed reference
    assert!(person_ref.name == "Joe");
}

which produces the following error

error[E0502]: cannot borrow `people` as mutable because it is also borrowed as immutable
  --> src/main.rs:21:5
   |
15 |     let person_ref = &people[0];
   |                       ------ immutable borrow occurs here
...
21 |     people.push(new_person);
   |     ^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
...
24 |     assert!(person_ref.name == "Joe");
   |             --------------- immutable borrow later used here

I've also tried boxing the vector elements as suggested here, but that doesn't help. I thought it might allow me to drop the reference to the vector while maintaining a reference to the element, but apparently not.

struct Person {
    name: String,
}

fn main() {
    // Create a mutable vector
    let mut people: Vec<Box<Person>> = ["Joe", "Shavawn", "Katie"]
        .iter()
        .map(|&s| {
            Box::new(Person {
                name: s.to_string(),
            })
        })
        .collect();

    // Borrow a reference to an element
    let person_ref = people[0].as_ref();

    // Mutate the vector
    let new_person = Box::new(Person {
        name: "Tim".to_string(),
    });
    people.push(new_person);

    // Attempt to use the borrowed reference
    assert!(person_ref.name == "Joe");
}

This still produces the same error

error[E0502]: cannot borrow `people` as mutable because it is also borrowed as immutable
  --> src/main.rs:23:5
   |
17 |     let person_ref = people[0].as_ref();
   |                      ------ immutable borrow occurs here
...
23 |     people.push(new_person);
   |     ^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
...
26 |     assert!(person_ref.name == "Joe");
   |             --------------- immutable borrow later used here

Is there a way to do this, or am I trying to do something impossible?


Solution

  • I found that using a reference counted smart pointer allows me to accomplish what I'm attempting. It makes sense that a shared ownership is necessary, because otherwise the element reference would become invalid if the original vector were to go out of scope (which would deallocate the element, with or without the Box).

    The following code compiles successfully.

    use std::rc::Rc;
    
    struct Person {
        name: String,
    }
    
    fn main() {
        // Create a mutable vector
        let mut people: Vec<Rc<Person>> = ["Joe", "Shavawn", "Katie"]
            .iter()
            .map(|&s| {
                Rc::new(Person {
                    name: s.to_string(),
                })
            })
            .collect();
    
        // Borrow a reference to an element
        let person_ref = Rc::clone(&people[0]);
    
        // Mutate the vector
        let new_person = Rc::new(Person {
            name: "Tim".to_string(),
        });
        people.push(new_person);
    
        // Attempt to use the borrowed reference
        assert!(person_ref.name == "Joe");
    }
    

    If anyone else has any corrections, improvements or further insight, I'd be glad to hear it. But if not, I feel satisfied with this answer for now.