Can I mutate a vector with a borrowed element?
Asked Answered
M

2

3

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?

Minter answered 16/2, 2021 at 5:44 Comment(3)
Is this a theoretical or real-world example? The reason is that since the option to use assert!(people[0].name == "Joe"); is implicitly discarded, it's not clear if the question is theoretical, or it's a real-world one with a bigger picture that prevents the solution mentioned.Italicize
@Italicize - I'm not sure what you mean "the option to use ... is implicitly discarded". That was just a way to attempt to access people[0].name. The question does come from a real-world example of attempting to fill a mutable vector in a loop while storing references to the elements in a hash map. Context.Minter
The context clarifies everything :) I think there's no better solution than your answer in this case, given that one reference may go out of scope.Italicize
M
3

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.

Minter answered 16/2, 2021 at 5:44 Comment(7)
"otherwise the element reference would become invalid if the original vector were to go out of scope" the vector can't go out of scope here, the issue is that mutating the vector can invalidate the reference entirely. Specifically here, push can cause a resize of the vector, which may require the contents of the vector to be moved to a new allocation. The reference would therefore become dangling.Therine
So do you think there is a way to do this using Box instead of Rc, then?Minter
Well yes and no, at a technical level the indirection would be there but it would bee rejected because the type system would not understand it. So if you absolutely have to do this Rc seems like the least bad way to do it.Therine
It sounds like your're really saying "just no". If you know of a way, could you provide an example?Minter
@Oliver You could do it with unsafe code, but that muddies the ownership and doesn't scale well. Another common thing to do in Rust is store indices into a vector rather than references to its items (example).Nadene
I don't see the point here. This is a lot of hassle, while cloning of people[0] would get the job done: #[derive(Clone)] over struct Person and let person_ref = people[0].clone();Maley
@Nadene Mutate the vector with people.insert(0, new_person);Maley
W
3

While the answer given by Oliver seems to be the best solution to this,

But if we want to only mutate the vector(as stated by the question title), that is, not push any more elements(as vectors are handled complexly), just modify values, we can use the function split_at_mut() given by Vec.

let (head, tail) = people.split_at_mut(1);

now we can let something borrow head while we mutate tail. But we will not be able to push to it because both head and tail are slices.

Wieche answered 23/5, 2023 at 20:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.