Reputation: 4077
How can I modify a Vec
based on information from an item within the Vec
without having both immutable and mutable references to the vector?
I've tried to create a minimal example that demonstrates my specific problem. In my real code, the Builder
struct is already the intermediate struct that other answers propose. Specifically, I do not think this question is answered by other questions because:
Vec<Item>
. The value being modified/operated on is Vec<Item>
and is what would need to be in the intermediate structSuppose I have a list of item definitions, where items are either a String, a nested list of Item
s, or indicate that a new item should be added to the list of items being processed:
enum Item {
Direct(String),
Nested(Vec<Item>),
New(String),
}
There is also a builder that holds a Vec<Item>
list, and builds an item at the specified index:
struct Builder {
items: Vec<Item>,
}
impl Builder {
pub fn build_item(&mut self, item: &Item, output: &mut String) {
match item {
Item::Direct(v) => output.push_str(v),
Item::Nested(v) => {
for sub_item in v.iter() {
self.build_item(sub_item, output);
}
}
Item::New(v) => self.items.push(Item::Direct(v.clone())),
}
}
pub fn build(&mut self, idx: usize, output: &mut String) {
let item = self.items.get(idx).unwrap();
self.build_item(item, output);
}
}
This doesn't compile due to the error:
error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
--> src/main.rs:26:9
|
25 | let item = self.items.get(idx).unwrap();
| ---------- immutable borrow occurs here
26 | self.build_item(item, output);
| ^^^^^----------^^^^^^^^^^^^^^
| | |
| | immutable borrow later used by call
| mutable borrow occurs here
error: aborting due to previous error
For more information about this error, try `rustc --explain E0502`.
I don't know how to have the Builder
struct be able to modify its items
(i.e. have a mutable reference to self.items
) based on information contained in one of the items (i.e. an immutable borrow of self.items
).
Here is a playground example of the code.
Clone
@Stargateur recommended that I try cloning the item in build()
. While this does work, I've been trying to not clone items for performance reasons. UPDATE: Without adding the Vec<Item>
modification feature with Item::New
, I implemented the clone()
method in my real code and cloned the value in the equivalent of the example build()
method above. I saw a 12x decrease in performance when I do self.items.get(idx).unwrap().clone()
vs self.items.get(idx).unwrap()
. I will continue looking for other solutions. The problem is, I'm still relatively new to Rust and am not sure how to bend the rules/do other things even with unsafe code.
Code that does work (playground)
impl Clone for Item {
fn clone(&self) -> Self {
match self {
Item::Direct(v) => Item::Direct(v.clone()),
Item::Nested(v) => Item::Nested(v.clone()),
Item::New(v) => Item::New(v.clone()),
}
}
}
and change build
to clone the item first:
let item = self.items.get(idx).unwrap().clone();
Upvotes: 3
Views: 517
Reputation: 7579
Whenever approaching problems like this (which you will encounter relatively frequently while using Rust), the main goal should be to isolate the code requiring the immutable borrow from the code requiring the mutable borrow. If the borrow from the items
vec in build
is unavoidable (i.e. you cannot move the item out of self.items
or copy/clone it) and you must pass in a reference to this item to build_item
, you might want to consider rewriting your build_item
function to not mutate self
. In this case, build_item
only ever appends new items to the end of self.items
, which lets us make an interesting refactor: Rather than have build_item
modify items
, make it return the items to be added to the original vector, and then have the caller add the newly generated items to the items
vector.
impl Builder {
fn generate_items(&self, item: &Item, output: &mut String) -> Vec<Item> {
match item {
Item::Direct(v) => {
output.push_str(v);
Vec::new()
}
Item::Nested(v) => {
v.iter()
.flat_map(|sub_item| self.generate_items(sub_item, output))
.collect()
}
Item::New(v) => vec![Item::Direct(v.clone())],
}
}
pub fn build_item(&mut self, item: &Item, output: &mut String) {
let mut new_items = self.generate_items(item, output);
self.items.append(&mut new_items);
}
pub fn build(&mut self, idx: usize, output: &mut String) {
// Non lexical lifetimes allow this to compile, as the compiler
// realizes that `item` borrow can be dropped before the mutable borrow
// Immutable borrow of self starts here
let item = self.items.get(idx).unwrap();
let mut new_items = self.generate_items(item, output);
// Immutable borrow of self ends here
// Mutable borrow of self starts here
self.items.append(&mut new_items);
}
}
Note that in order to preserve the API, your build_item
function has been renamed to generate_items
, and a new build_item
function that uses generate_items
has been created.
If you look carefully, you'll notice that generate_items
doesn't even require self
, and can be a free-standing function or a static function in Builder
.
Upvotes: 3