Reputation: 73
I am trying to build a solution to Graham´s accumulator factory challenge which basically requires a function to return a closure that closes over a mutable numeric variable which initial value is received through a parameter. Each call to this closure increments this captured variable by a value that is a parameter to the closure and returns the accumulated value.
After reading the closures RFC and some questions about returning unboxed closures (in particular this). I could finally come up with a solution that compiled, but the result is not what I would expect.
#![feature(unboxed_closures, unboxed_closure_sugar)]
fn accumulator_factory(n: f64) -> Box<|&mut: f64| -> f64> {
let mut acc = n;
box |&mut: i: f64| {
acc += i;
acc
}
}
fn main() {
let mut acc_cl = accumulator_factory(5f64);
println!("{}", acc_cl.call_mut((3f64,)));
println!("{}", acc_cl.call_mut((3f64,)));
}
AFAIK this closure captures acc
by value, the generated struct that acts as the environment is mutable and acc_cl
should retain the same environment instance between calls.
But the printed result is 6
in both cases, so it seems the modified value is not persisting. And what is more confusing is how this result is computed. On each execution of the closure the initial value of acc
is 3
even though n
is 5
when called.
If I modify the generator to this:
fn accumulator_factory(n: f64) -> Box<|&mut: f64| -> f64> {
println!("n {}", n);
let mut acc = n;
box |&mut: i: f64| {
acc += i;
acc
}
}
then the execution always return 3
and the initial value of acc
is always 0
on closure entry.
This difference in semantics looks like a bug. But apart from that, why is the environment reset between calls?
This was run with rustc 0.12.0.
Upvotes: 6
Views: 575
Reputation: 128051
The capture mode of closures has changed recently. Closures are biased to capture everything by reference because the most common use case for closures is passing them to functions down the call stack, and capturing by reference makes working with the environment more natural.
Sometimes capture by reference is limiting. For example, you can't return such closures from functions because their environment is tied to the call stack. For such closures you need to put the move
keyword before the closure:
fn accumulator_factory(n: f64) -> Box<FnMut(f64) -> f64> {
println!("n: {}", n);
let mut acc = n;
Box::new(move |i: f64| {
acc += i;
acc
})
}
fn main() {
let mut acc = accumulator_factory(10.0);
println!("{}", acc(12.0));
println!("{}", acc(12.0));
}
This program works as intended:
n: 10
22
34
These two closure kinds are covered by this RFC.
Upvotes: 8