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.
Rust's closures are anonymous functions you can save in a variable or pass as arguments to other functions. You can create the closure in one place and then call the closure elsewhere to evaluate it in a different context. Unlike functions, closures can capture values from the scope in which they're defined.
Closures are useful because they let you associate data (the lexical environment) with a function that operates on that data. This has obvious parallels to object-oriented programming, where objects allow you to associate data (the object's properties) with one or more methods.
A closure expression produces a closure value with a unique, anonymous type that cannot be written out. A closure type is approximately equivalent to a struct which contains the captured variables.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With