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:
- Why does refactoring by extracting a method trigger a borrow checker error? - What would I put in the intermediate struct? I'm not calculating a separate value from
Vec<Item>
. The value being modified/operated on isVec<Item>
and is what would need to be in the intermediate struct
Suppose 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.
Using 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();
Vec<Item>
to be able to construct the intermediate struct. Funny thing is, in my real codeBuilder
is the intermediate struct already – d0c_s4vagebuild
cause compiler can't know the borrow could end when you modify your vector (play.rust-lang.org/…). – StargateurClone
trait forItem
. This does work, but I'm not sure how big of a performance hit my real project will take. If I don't come up with anything else I'll mark it as an answer (if you make it a separate answer). – d0c_s4vageVec
, but without more context it's hard to know if this would be suitable for you. One important thing to recognize here is that Rust is not being a nuisance here with the compiler error: it is protecting you from actual memory corruption which would happen when the Vec reallocates and leaves your &Item dangling. – Brent Kerby