Reputation: 3750
I have the below problem which is hopefully pretty self explanatory. There are 4 progressing examples which are my attempts to isolate the issue. The first three work fine. The final example, where I am trying to use a getter method to get a mutable reference to some data and then mutate it, fails with "cannot borrow as immutable because it is also borrowed as mutable" which I can't understand because it seems to do the same thing as the previous examples.
Note: Not sure how to title this so will try and change title if an answer makes it clear what the actual problem is.
Note: I realise there also also a .retain_mut()
variation of .retain()
but as far as I can tell, it is not relevant here.
struct Example {
fruit: Vec<String>,
selected_fruit: Vec<String>,
}
impl Example {
fn get_fruit(&mut self) -> &mut Vec<String> {
&mut self.fruit
}
fn retain_selected_direct(&mut self) {
self.fruit
.retain(|fruit| self.selected_fruit.contains(fruit));
}
fn retain_selected_getter(&mut self) {
let fruit = self.get_fruit();
fruit.retain(|fruit| self.selected_fruit.contains(fruit));
}
}
fn main() {
// mut example
let mut example = Example {
fruit: vec![
"apple".to_string(),
"orange".to_string(),
"banana".to_string(),
],
selected_fruit: vec!["apple".to_string(), "banana".to_string()],
};
example
.fruit
.retain(|fruit| example.selected_fruit.contains(&fruit));
dbg!(&example.fruit);
// &mut example
let mut example = Example {
fruit: vec![
"apple".to_string(),
"orange".to_string(),
"banana".to_string(),
],
selected_fruit: vec!["apple".to_string(), "banana".to_string()],
};
let example_ref = &mut example;
example_ref
.fruit
.retain(|fruit| example_ref.selected_fruit.contains(&fruit));
dbg!(&example.fruit);
// &mut self direct
let mut example = Example {
fruit: vec![
"apple".to_string(),
"orange".to_string(),
"banana".to_string(),
],
selected_fruit: vec!["apple".to_string(), "banana".to_string()],
};
example.retain_selected_direct();
dbg!(&example.fruit);
// &mut self getter
let mut example = Example {
fruit: vec![
"apple".to_string(),
"orange".to_string(),
"banana".to_string(),
],
selected_fruit: vec!["apple".to_string(), "banana".to_string()],
};
example.retain_selected_getter();
dbg!(&example.fruit);
}
The error is:
error[E0502]: cannot borrow `self.selected_fruit` as immutable because it is also borrowed as mutable
--> src/main.rs:15:22
|
14 | let fruit = self.get_fruit();
| ---------------- mutable borrow occurs here
15 | fruit.retain(|fruit| self.selected_fruit.contains(fruit));
| ------ ^^^^^^^ ------------------- second borrow occurs due to use of `self.selected_fruit` in closure
| | |
| | immutable borrow occurs here
| mutable borrow later used by call
Upvotes: 1
Views: 633
Reputation: 3750
As per Chayim's answer:
Usually, the borrow checker can reason about fields independently. It knows that you can borrow self.fruit and self.selected_fruit mutably together, because they're disjoint fields. But when using the getter, this information is lost: the compiler doesn't look into the getter, and thus consider it as borrowing the whole object. Any attempt to also borrow some field of the object thus raises an error.
I can therefore solve the problem by pulling the fruit vector out into a separate type for which the "getter" (in my use case actually just a helper function for getting a subset of fruit) is defined, therefore it is now clear that to the compiler that .retain_selected_getter()
is using two distinct fields.
struct Fruit {
fruit: Vec<String>,
}
impl Fruit {
fn get_fruit(&mut self) -> &mut Vec<String> {
&mut self.fruit
}
}
struct Example {
fruit: Fruit,
selected_fruit: Vec<String>,
}
impl Example {
fn retain_selected_getter(&mut self) {
let fruit = self.fruit.get_fruit();
fruit.retain(|fruit| self.selected_fruit.contains(fruit));
}
}
fn main() {
// &mut self getter
let mut example = Example {
fruit: Fruit {
fruit: vec![
"apple".to_string(),
"orange".to_string(),
"banana".to_string(),
],
},
selected_fruit: vec!["apple".to_string(), "banana".to_string()],
};
example.retain_selected_getter();
dbg!(&example.fruit.fruit);
}
Upvotes: 0
Reputation: 70850
Usually, the borrow checker can reason about fields independently. It knows that you can borrow self.fruit
and self.selected_fruit
mutably together, because they're disjoint fields. But when using the getter, this information is lost: the compiler doesn't look into the getter, and thus consider it as borrowing the whole object. Any attempt to also borrow some field of the object thus raises an error.
For this reason, getters/setters are generally discouraged in Rust. I think they're also not good generally: if anybody can access your field, make it pub
. Otherwise, most likely it is some internal data and thus should not be exposed either, because its raw form doesn't make sense. In the rare cases that left where the field should really be public but has some invariant you need to enforce, use a newtype that enforces this invariant instead of a setter. This has the additional advantage that you can differentiate between e.g. String
and Email
, and makes the code intent clearer.
There are some cases this is limiting, and there are some ideas on how to improve this, like the post @SvenMarnach linked, After NLL: Interprocedural conflicts, or the newer post from @nikomatsakis about the same thing, View types for Rust. But none of these has been accepted yet, let alone implemented.
Upvotes: 2