Juan Vidal
Juan Vidal

Reputation: 73

Returning a closure with mutable environment

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

Answers (1)

Vladimir Matveev
Vladimir Matveev

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

Related Questions