Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Returning a closure with mutable environment

Tags:

closures

rust

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.

like image 631
Juan Vidal Avatar asked Oct 22 '14 15:10

Juan Vidal


People also ask

Does Rust support closures?

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.

Why use closures?

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.

What is closure type?

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.


1 Answers

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.

like image 174
Vladimir Matveev Avatar answered Nov 15 '22 07:11

Vladimir Matveev