I have a struct with two methods. The first method returns an iterator over some field. The second method calls the first one and subsequently tries to modify that field (Playground):
struct Container {
vec: Vec<f64>,
}
impl Container {
fn large_positions<'a>(&'a self) -> impl Iterator<Item = usize> + 'a {
self.vec
.iter()
.enumerate()
.filter_map(|(i, &f)| if f > 3.14 { Some(i) } else { None })
}
fn negate_first_large_entry(&mut self) {
if let Some(i) = self.large_positions().next() {
self.vec[i] *= -1.0;
}
}
}
The borrow checker complains:
error[E0502]: cannot borrow `self.vec` as mutable because it is also borrowed as immutable
--> src/lib.rs:15:13
|
14 | if let Some(i) = self.large_positions().next() {
| ----------------------
| |
| immutable borrow occurs here
| a temporary with access to the immutable borrow is created here ...
15 | self.vec[i] *= -1.0;
| ^^^^^^^^ mutable borrow occurs here
16 | }
17 | }
| - ... and the immutable borrow might be used here, when that temporary is dropped and runs the destructor for type `impl Iterator`
If I manually inline large_positions
, the code compiles so the destructor thing is not a real issue.
Why does the borrow checker know that there is no conflict in the inlined version? And how can I explain this to the borrow checker in the non-inlined version?
Update: The following slightly different version of the second function exhibits the same behavior: (Playground)
fn negate_first_large_entry_below_100(&mut self) {
for i in self.large_positions() {
if self.vec[i] < 100.0 {
self.vec[i] *= -1.0;
return;
}
}
}
Here the return
statement causes the borrow checker to understand that the borrow of self.vec
can be released as soon as the if
branch is entered. But again this only works if large_positions
is inlined.
Ideally I would like to modify the signature of large_positions
in such a way that the borrow checker is still able to perform this clever "early releasing".