代码之家  ›  专栏  ›  技术社区  ›  Jeremy Meo

可以使用impl Fn接受任意大小的闭包作为参数吗?

  •  0
  • Jeremy Meo  · 技术社区  · 7 年前

    我有一个 History 包含初始状态、当前状态和闭包列表的类型,其中包含从初始状态计算当前状态所需的每个更改。这些应用于 .apply(...) 方法获取一个装箱的闭包,使用它修改当前状态,并将其添加到列表中。因为我希望这些可以被确定地重复使用 Fn ,不是 FnMut FnOnce .

    struct History<State: Clone> {
        initial: State,
        current: State,
        updates: Vec<Box<dyn Fn(&mut State)>>,
    }
    
    impl<State: Clone> History<State> {
        fn apply(&mut self, update: Box<dyn Fn(&mut State)>) {
            update(&mut self.current);
            self.updates.push(update);
        }
    }
    

    我现在把闭包当作 Box<dyn Fn(&mut State)> ,而且效果很好:

    fn main() {
        let mut history = History::<usize> {
            initial: 0,
            current: 0,
            updates: vec![],
        };
    
        let delta = 10;
        history.apply(Box::new(move |mut value| *value += delta));
    
        println!("{}", history.current);
    }
    
    10
    

    这让我想到了一个方法是否可能接受任意的 未装箱 使用关闭 impl Trait 而不是 dyn Trait . 在这种情况下,我们的方法可以将闭包本身装箱,因此调用站点将变成:

        history.apply(move |mut value| *value += delta);
    

    (请考虑这样做是否可能,即使在这种情况下这是个坏主意。)

    我认为每个闭包站点都像一个不同的数据类型,每次使用时都会用封闭的值实例化,所以 impl Trait可以专门化每个隐式闭包的方法,就像它对每个显式类型所做的那样。但我不确定生锈是否真的像那样。

    但是,当我尝试更改代码时,会出现一个新的终生错误:

        fn apply(&mut self, update: impl Fn(&mut State)) {
            update(&mut self.current);
            self.updates.push(Box::new(update));
        }
    
    error[E0310]: the parameter type `impl Fn(&mut State)` may not live long enough
      --> src/main.rs:10:27
       |
    10 |         self.updates.push(Box::new(update));
       |                           ^^^^^^^^^^^^^^^^
       |
    note: ...so that the type `impl Fn(&mut State)` will meet its required lifetime bounds
      --> src/main.rs:10:27
       |
    10 |         self.updates.push(Box::new(update));
       |                           ^^^^^^^^^^^^^^^^
    

    这让我很困惑。我不确定哪里有不好的参考资料。

    在我的脑子里,整个关闭状态现在被转移到 apply 通过 impl Fn 参数,然后移动到 Box 属于 self . 但它抱怨我不能将内容移动到一个框中,因为我有一个潜在的过时引用,而不仅仅是拥有的数据?我在哪里借钱?为什么当我把闭包直接装进 main 而不是在 应用 ?


    可以用吗 执行Fn 接受(任意大小的)闭包作为参数?如果是,怎么做?

    1 回复  |  直到 7 年前
        1
  •  3
  •   Shepmaster Tim Diekmann    7 年前

    可以使用impl Fn接受任意大小的闭包作为参数吗?

    对。

    impl trait 在参数中的位置与泛型完全相同。它们是相同的:

    fn foo1(_: impl Fn(u8) -> i8) {}
    
    fn foo2<F>(_: F)
    where
        F: Fn(u8) -> i8,
    {}
    

    这实际上是 一般优先 接受闭包(或许多其他特征实现)的方法,因为它允许编译器 单晶层 结果并避免任何不必要的间接操作。


    编译代码时会显示以下帮助文本( which currently has some rendering glitches ):

    help: consider adding an explicit lifetime bound `impl Fn(&mut State): 'static`...
       |
    8  |     fn apply(&mut self, update: impl Fn(&mut State): 'static +  {
       |                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    

    意思是说 + 'static :

    fn apply(&mut self, update: impl Fn(&mut State) + 'static)
    

    这很管用。

    另见: