首页 文章

返回在Rust中捕获外部变量的闭包

提问于
浏览
3

正如 Headers 所述,我希望从一个具有初始可变状态的函数返回一个闭包 . 在以下示例中, 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; })

这对我来说完全是个谜 .

2 回答

  • 2

    你需要在这里做两件事:make res mutable,并返回 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]:当然,除非你涉及内部可变性 .

  • 4

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

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

    我们可以从 agg0 's return type that the type of the closure'推断出第二个参数是 &mut i32 . &mut acc 是一种解构可变引用的模式,将 acc 定义为 i32 ,初始化为引用值的副本 . 您可以't mutate it, because you didn' t将 acc 定义为可变(您需要编写 &mut mut acc 而不是 &mut acc ),但's not what you want anyway, because then you' d正在改变副本 . 你想要的是改变指向的整数,所以你需要像这样定义你的闭包:

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

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

相关问题