Reputation: 1554
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
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
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