josh
josh

Reputation: 1554

Returning closures which capture outer variables in Rust

As the title states I am looking to return a closure from a function which has some initial, mutable state. In the following examples CowRow is a struct with a time field. It also has a String field, so is thus not copyable. Concretely, I would like a function that looks something like:

pub fn agg1() -> Box<Fn(&CowRow)> {
    let res = 0;
    Box::new(move |r| { res += r.time; })
}

Of course, this produces the error:

src/queries.rs:9:25: 9:38 error: cannot assign to captured outer variable in an `Fn` closure
src/queries.rs:9     Box::new(move |r| { res += r.time; })
                                         ^~~~~~~~~~~~~
src/queries.rs:9:14: 9:41 help: consider changing this closure to take self by mutable reference
src/queries.rs:9     Box::new(move |r| { res += r.time; })
                              ^~~~~~~~

It is my understanding that Rust needs to know about the size of returned values and because closures borrow their stack frame from their environment we need to introduce the Box and move to get a size for the return and put the closure on the heap.

Is there some way to also put res on the heap in this closures environment? Or otherwise allow for this behaviour? Of course I have looked at: Cannot borrow captured outer variable in an `Fn` closure as mutable but this seems overly complicated and it's not clear to me how this would perform in the case of multiple threads running this function simultaneously.

Another technique I tried was to change the closure to take a mutable reference to an i32 which I can initialise outside of the agg function. Example:

pub fn agg0() -> Box<Fn(&CowRow, &mut i32)> {
    Box::new(move |r, &mut acc| { acc += r.time; })
}

However, this produces the error:

src/queries.rs:4:35: 4:48 error: re-assignment of immutable variable `acc` [E0384]
src/queries.rs:4     Box::new(move |r, &mut acc| { acc += r.time; })
                                                   ^~~~~~~~~~~~~
src/queries.rs:4:35: 4:48 help: run `rustc --explain E0384` to see a detailed explanation
src/queries.rs:4:28: 4:31 note: prior assignment occurs here
src/queries.rs:4     Box::new(move |r, &mut acc| { acc += r.time; })

This one is a total mystery to me.

Upvotes: 4

Views: 2804

Answers (2)

Francis Gagn&#233;
Francis Gagn&#233;

Reputation: 65937

DK. already said how to fix agg1, but I wanted to explain what's wrong with agg0, for completeness.

pub fn agg0() -> Box<Fn(&CowRow, &mut i32)> {
    Box::new(move |r, &mut acc| { acc += r.time; })
}

We can infer from agg0's return type that the type of the closure's second parameter is &mut i32. &mut acc is a pattern that deconstructs the mutable reference, defining acc as an i32, initialized to a copy of the referenced value. You can't mutate it, because you didn't define acc to be mutable (you'd need to write &mut mut acc instead of &mut acc), but that's not what you want anyway, because then you'd be mutating a copy. What you want is to mutate the pointed-to integer, so you need to define your closure like this:

pub fn agg0() -> Box<Fn(&CowRow, &mut i32)> {
    Box::new(move |r, acc| { *acc += r.time; })
}

Here, the type of acc is &mut i32, so in order to mutate the i32, we need to dereference the pointer first (this yields an lvalue that refers to the i32 behind the pointer; it's not a copy!).

Upvotes: 2

DK.
DK.

Reputation: 59155

You need to do two things here: make res mutable, and return an FnMut closure, not an Fn one:

pub struct CowRow {
    time: u64,
}

pub fn agg1() -> Box<FnMut(&CowRow) -> u64> {
    let mut res = 0;
    Box::new(move |r| { res += r.time; res })
}

fn main() {
    let mut c = agg1();
    let moo = CowRow { time: 2 };
    println!("{:?}", c(&moo));
    println!("{:?}", c(&moo));
    println!("{:?}", c(&moo));
}

The Fn trait forbids the implementor from changing itself when invoked. Since this closure is modifying its own state, this means it cannot be Fn [1]. Instead, you need to use FnMut which does allow mutation of the closure's captured environment.


[1]: Unless you involve interior mutability, of course.

Upvotes: 4

Related Questions