I have code analogous to:
struct L {
length: usize,
count: usize,
}
impl L {
fn iter(&self, ns: impl Iterator<Item=usize>) -> impl Iterator<Item=usize> {
ns.filter(|&n| self.length > n)
}
}
fn testt() {
let mut myl = L { length: 0, count: 0 };
let ns = [0, 1, 2, 3];
for n in myl.iter(ns.into_iter()) {
myl.count += n;
}
}
This fails with error[E0506]: cannot assign to 'myl.count' because it is borrowed
.
I understand that &self
is being captured for the duration of the loop and preventing the borrowing of myl
later, but my question is whether there is a way to avoid this?
And if there isn't, why? It seems like perfectly safe code since its equivalent to:
struct L {
length: usize,
count: usize,
}
fn testt() {
let mut myl = L { length: 0, count: 0 };
let ns = [0, 1, 2, 3];
for n in ns.into_iter() {
if myl.length > n {
myl.count += n;
}
}
}
which does compile. I don't really understand why references used inside closures in this case wouldn't be recaptured on function call, rather than having a lifetime that extends to the end of the iteration.
I know I could collect the result from iter()
, but there are performance constraints in this project that would make me want to avoid large heap allocations.
In general it's not possible to have a closure that releases one of it's captures in between each time it gets called. You'd usually replace that capture with a parameter to the closure, but that's not really an option here.
You do have a way out for your example though and the complete error gives you a hint how to achieve it:
error[E0506]: cannot assign to `myl.count` because it is borrowed
--> src/lib.rs:15:9
|
14 | for n in myl.iter(ns.into_iter()) {
| ------------------------
| |
| `myl.count` is borrowed here
| borrow later used here
15 | myl.count += n;
| ^^^^^^^^^^^^^^ `myl.count` is assigned to here but it was already borrowed
|
note: this call may capture more lifetimes than intended, because Rust 2024 has adjusted the `impl Trait` lifetime capture rules
--> src/lib.rs:14:14
|
14 | for n in myl.iter(ns.into_iter()) {
| ^^^^^^^^^^^^^^^^^^^^^^^^
note: you could use a `use<...>` bound to explicitly specify captures, but argument-position `impl Trait`s are not nameable
--> src/lib.rs:6:24
|
6 | fn iter(&self, ns: impl Iterator<Item=usize>) -> impl Iterator<Item=usize> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^
help: use the precise capturing `use<...>` syntax to make the captures explicit
|
6 - fn iter(&self, ns: impl Iterator<Item=usize>) -> impl Iterator<Item=usize> {
6 + fn iter<T: Iterator<Item = usize>>(&self, ns: T) -> impl Iterator<Item=usize> + use<T> {
|
One part of the solution is to use the precise capturing syntax to tell Rust that you're not actually borrowing self
in the returned iterator.
The problem is, that |&n| self.length > n
does borrow self
, so we have to avoid it, which is easy here since length
is an usize
which we can simply copy and move into the closure:
impl L {
fn iter<T: Iterator<Item = usize>>(&self, ns: T) -> impl Iterator<Item = usize> + use<T> {
let length = self.length;
ns.filter(move |&n| length > n)
}
}