返回一个带有可变环境的闭包

6
我正在尝试构建一个解决方案Graham´s accumulator factory challenge,它基本上需要一个函数返回一个闭包,该闭包闭合一个可变数值变量,其初始值通过参数接收。每次调用此闭包时,都会将捕获的变量增加一个作为闭包参数的值,并返回累积值。
阅读了closures RFC和一些关于返回未装箱闭包的问题(特别是this)后,我终于想出了一个编译的解决方案,但结果并不是我所期望的。
#![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,)));
}

据我所知,此闭包通过值捕获了acc,生成的结构充当环境是可变的,acc_cl应该在调用之间保留相同的环境实例。
但是,打印的结果在两种情况下都是6,因此修改后的值似乎没有持续。更令人困惑的是如何计算此结果。每次执行闭包时,acc的初始值都为3,即使在调用时n5
如果我将生成器修改为以下内容:
fn accumulator_factory(n: f64) -> Box<|&mut: f64| -> f64> {
    println!("n {}", n);
    let mut acc = n;
    box |&mut: i: f64| {
        acc += i; 
        acc
    }
}

然后执行总是返回3,并且在闭包进入时acc的初始值始终为0

这种语义上的差异看起来像是一个bug。除此之外,为什么会在调用之间重置环境呢?

这是使用rustc 0.12.0运行的。


1
另一种方法可以查看此解决方案:https://github.com/Hoverbear/rust-rosetta/blob/master/src/accumulator_factory.rs。Rust中的新未打包闭包是结构体和特征实现的语法糖。此版本编写了冗长的“未打糖”版本。 - Paolo Falabella
1个回答

8
最近闭包的捕获模式发生了变化。闭包被倾向于通过引用来捕获所有内容,因为最常见的使用场景是将它们传递给调用栈下面的函数,而通过引用捕获环境更加自然。但有时引用捕获会受到限制。例如,你不能从函数中返回这样的闭包,因为它们的环境与调用栈相关联。对于这样的闭包,需要在闭包前加上move关键字。
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));
}

这个程序按照预期工作:
n: 10
22
34

这两种闭包类型在 RFC中得到解释。


你能否调用 acc(12.0) 而不是 acc.call_mut((12.0,))?后者的调用语法似乎有些繁琐。 - Matthieu M.
@MatthieuM.,这就是我说我不明白为什么重载调用不起作用的原因。它们也被特性门控,但即使解除了这个门,编译器仍然会抱怨。 - Vladimir Matveev
@VladimirMatveev 谢谢。我不知道语法有这样的变化。我想事情仍然在非常快速地变化。现在,当我尝试将f64更改为通用类型时,我遇到了生命周期问题。但我会写另一个问题来解决它。 - Juan Vidal
我编辑了上面的代码,使其在 Rust 1.9 nightly 上运行。未装箱的闭包已经实现,并且根据以下语法进行了更改:https://github.com/rust-lang/rfcs/blob/master/text/0231-upvar-capture-inference.md - CMCDragonkai

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接