loopsrustiteratorborrow-checker

Is there a way to release and recapture references in closures while iterating?


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.


Solution

  • 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)
        }
    }