【问题标题】:Returning closures which capture outer variables in Rust返回在 Rust 中捕获外部变量的闭包
【发布时间】:2016-04-08 23:57:42
【问题描述】:

正如标题所述,我希望从具有一些初始可变状态的函数返回闭包。在以下示例中,CowRow 是带有 time 字段的 struct。它还有一个String 字段,因此不可复制。具体来说,我想要一个看起来像这样的函数:

pub fn agg1() -> Box<Fn(&CowRow)> {
    let res = 0;
    Box::new(move |r| { res += r.time; })
}

当然,这会产生错误:

src/queries.rs:9:25: 9:38 error: cannot assign to captured outer variable in an `Fn` closure
src/queries.rs:9     Box::new(move |r| { res += r.time; })
                                         ^~~~~~~~~~~~~
src/queries.rs:9:14: 9:41 help: consider changing this closure to take self by mutable reference
src/queries.rs:9     Box::new(move |r| { res += r.time; })
                              ^~~~~~~~

据我了解,Rust 需要知道返回值的大小,并且由于闭包从它们的环境中借用了它们的堆栈框架,我们需要引入 Boxmove 来获得返回值的大小并将堆上的闭包。

有没有办法在这个闭包环境中也将res 放在堆上?或者以其他方式允许这种行为?当然我看过:Cannot borrow captured outer variable in an `Fn` closure as mutable,但这似乎过于复杂,我不清楚在多个线程同时运行此功能的情况下这将如何执行。

我尝试的另一种技术是更改闭包以获取对i32 的可变引用,我可以在agg 函数之外对其进行初始化。示例:

pub fn agg0() -> Box<Fn(&CowRow, &mut i32)> {
    Box::new(move |r, &mut acc| { acc += r.time; })
}

但是,这会产生错误:

src/queries.rs:4:35: 4:48 error: re-assignment of immutable variable `acc` [E0384]
src/queries.rs:4     Box::new(move |r, &mut acc| { acc += r.time; })
                                                   ^~~~~~~~~~~~~
src/queries.rs:4:35: 4:48 help: run `rustc --explain E0384` to see a detailed explanation
src/queries.rs:4:28: 4:31 note: prior assignment occurs here
src/queries.rs:4     Box::new(move |r, &mut acc| { acc += r.time; })

这对我来说完全是个谜。

【问题讨论】:

    标签: closures rust lifetime


    【解决方案1】:

    您需要在这里做两件事:使res 可变,并返回一个FnMut 闭包,而不是一个Fn

    pub struct CowRow {
        time: u64,
    }
    
    pub fn agg1() -> Box<FnMut(&CowRow) -> u64> {
        let mut res = 0;
        Box::new(move |r| { res += r.time; res })
    }
    
    fn main() {
        let mut c = agg1();
        let moo = CowRow { time: 2 };
        println!("{:?}", c(&moo));
        println!("{:?}", c(&moo));
        println!("{:?}", c(&moo));
    }
    

    Fn trait 禁止实现者在调用时改变自身。由于这个闭包正在修改它自己的状态,这意味着它不能是Fn [1]。相反,您需要使用FnMut,它确实允许对闭包捕获的环境进行突变。


    [1]:当然,除非您涉及内部可变性。

    【讨论】:

    • 完美,谢谢。在这种情况下,当我们move 关闭堆时,我们也在移动它的环境的可变版本,因为我们已将其声明为FnMut,这是否正确?那么,将返回类型声明为FnMut 改变了move 的语义?另外,我不是你所说的interior mutability
    • @Josh Data itself 在 Rust 中不是可变的或不可变的;这是您如何访问它的属性。也就是说,是的;闭包按值捕获res,并且由于我们将闭包移到堆上,因此捕获的res 随之而来。另外,请参阅本书中的Interior vs. Exterior Mutability
    【解决方案2】:

    DK. 已经说过如何修复agg1,但为了完整起见,我想解释一下agg0 的问题。

    pub fn agg0() -> Box<Fn(&CowRow, &mut i32)> {
        Box::new(move |r, &mut acc| { acc += r.time; })
    }
    

    我们可以从agg0的返回类型推断出闭包的第二个参数的类型是&amp;mut i32&amp;mut acc 是一种解构可变引用的模式,将acc 定义为i32,初始化为引用值的副本。你不能改变它,因为你没有将acc 定义为可变的(你需要写&amp;mut mut acc 而不是&amp;mut acc),但这不是你想要的,因为那样你就会成为变异一个副本。你想要的是改变指向的整数,所以你需要像这样定义你的闭包:

    pub fn agg0() -> Box<Fn(&CowRow, &mut i32)> {
        Box::new(move |r, acc| { *acc += r.time; })
    }
    

    这里,acc 的类型是&amp;mut i32,所以为了改变i32,我们需要首先取消对指针的引用(这会产生一个左值,它引用指针后面的i32;它是不是副本!)。

    【讨论】:

    • 这很棒。感谢您为答案添加更多详细信息!
    猜你喜欢
    • 1970-01-01
    • 2019-10-14
    • 1970-01-01
    • 2016-02-13
    • 2021-12-19
    • 1970-01-01
    • 2014-06-05
    • 2015-08-14
    相关资源
    最近更新 更多