I'm trying to write a method for a struct which has a Vec
as a member. The function I'd like to write, remove_thing
, should take a reference to an element of the Vec
and remove it, leaving the rest in place.
I'm getting a borrow checker error, that I cannot borrow the Vec
as mutable, because I have a borrow on the element as immutable. My expectation would be that there is still a valid way to express this, as I'm not actually mutating the interior element, just the containing Vec
. But I could be wrong.
Here's a short reproduction:
struct Thing {
name: String,
data: String,
}
struct HasSomeThings {
things: Vec<Thing>,
}
impl HasSomeThings {
fn thing_by_name(&self, name: &str) -> Option<&Thing> {
for t in self.things.iter() {
if t.name == name {
return Some(&t);
}
}
None
}
// Is it impossible to implement this function?
// It seems having a reference to a Thing in the vector prevents
// you from removing it from that vector.
fn remove_thing(&mut self, thing: &Thing) {
self.things.retain(|t| t.name != thing.name);
}
}
fn main() {
// Imagine this comes from reading a file somewhere
let mut some_things = HasSomeThings {
things: vec![
Thing { name: "one".to_string(), data: "one data".to_string() },
Thing { name: "two".to_string(), data: "two data".to_string() },
Thing { name: "three".to_string(), data: "three data".to_string() },
]
};
// Imagine this comes from user input
let name = "one";
let one_thing = some_things.thing_by_name(name).unwrap();
println!("{}", one_thing.data);
some_things.remove_thing(one_thing);
}
The error I'm seeing is:
Compiling playground v0.0.1 (/playground)
error[E0502]: cannot borrow `some_things` as mutable because it is also borrowed as immutable
--> src/main.rs:44:5
|
41 | let one_thing = some_things.thing_by_name(name).unwrap();
| ----------- immutable borrow occurs here
...
44 | some_things.remove_thing(one_thing);
| ^^^^^^^^^^^^------------^^^^^^^^^^^
| | |
| | immutable borrow later used by call
| mutable borrow occurs here
For more information about this error, try `rustc --explain E0502`.
error: could not compile `playground` (bin "playground") due to 1 previous error
Is there some trick I can do to implement the remove_thing
function? Or is there a reasonable way to restructure my code so that I can both work with a reference to the element from the Vec
and then later remove it? Do I just need to rebuild the whole HasSomeThings
instance from scratch?
Answers
The issue you're encountering is due to Rust's borrow checker enforcing the ownership rules. When you call thing_by_name
, you're borrowing self
immutably. Later, when you try to call remove_thing
, you're attempting to borrow self
mutably while it's still borrowed immutably, which Rust doesn't allow.
To solve this, you can restructure your code slightly. Instead of passing a reference to the Thing
to remove_thing
, you can pass the index of the Thing
to be removed. Since you're removing by name, you can first find the index of the Thing
by name, then remove it by index.
Here's how you can do it:
impl HasSomeThings {
fn thing_by_name(&self, name: &str) -> Option<usize> {
self.things.iter().position(|t| t.name == name)
}
fn remove_thing(&mut self, index: usize) {
self.things.remove(index);
}
}
fn main() {
let mut some_things = HasSomeThings {
things: vec![
Thing { name: "one".to_string(), data: "one data".to_string() },
Thing { name: "two".to_string(), data: "two data".to_string() },
Thing { name: "three".to_string(), data: "three data".to_string() },
]
};
let name = "one";
if let Some(index) = some_things.thing_by_name(name) {
let one_thing = &some_things.things[index];
println!("{}", one_thing.data);
some_things.remove_thing(index);
}
}
In this version, thing_by_name
returns an Option<usize>
representing the index of the Thing
with the given name. Then, in main
, if a Thing
with the given name is found, it's removed from the vector using its index. This way, you avoid borrowing self
immutably and mutably at the same time, resolving the borrow checker error.