如何编写一个包含闭包集合的函数?

时间:2017-07-30 10:20:25

标签: rust

我正在尝试编写一个函数,它接受类型为Fn() -> ()的闭包的集合,即每个闭包不带args,什么都不返回(我希望它们实际上是FnOnce,以便将其所有环境移动到闭包对象中。)

我尝试了很多东西(比如使用Box<Fn() -> ()>并使用&'static),但我无法让它发挥作用。

我在Rust Playground中创建了一个要点,以显示我正在尝试做的事情。

以下是简化代码:

fn run_all_tests<I>(tests: I)
where
    I: IntoIterator<Item = Box<FnOnce() -> ()>>,
{
}

fn main() {
    let examples = [1, 2, 3];

    run_all_tests(examples.iter().map(
        |ex| Box::new(move |ex| assert!(ex > 0)),
    ));
}

错误:

error[E0271]: type mismatch resolving `<[closure@src/main.rs:11:9: 11:49] as std::ops::FnOnce<(&{integer},)>>::Output == std::boxed::Box<std::ops::FnOnce() + 'static>`
  --> src/main.rs:10:5
   |
10 |     run_all_tests(examples.iter().map(
   |     ^^^^^^^^^^^^^ expected closure, found trait std::ops::FnOnce
   |
   = note: expected type `std::boxed::Box<[closure@src/main.rs:11:23: 11:48]>`
              found type `std::boxed::Box<std::ops::FnOnce() + 'static>`
   = note: required because of the requirements on the impl of `std::iter::Iterator` for `std::iter::Map<std::slice::Iter<'_, {integer}>, [closure@src/main.rs:11:9: 11:49]>`
   = note: required by `run_all_tests`

1 个答案:

答案 0 :(得分:2)

代码有几个问题:

  1. 您的盒装闭包采用参数ex,但特征FnOnce()不带参数。参数ex也会遮盖外部闭包中的参数ex,所以我假设你的意思是内部闭包不带参数:move || assert!(ex > 0)

  2. 由于比较了对非引用的引用,因此ex > 0中的类型不匹配。可以通过在模式匹配期间解除引用外部闭包参数来修复:|&ex| ....

  3. 类型推断不足以发现map返回的迭代器应该超过Box<FnOnce()>而不是Box<unique closure object>。您可以添加明确的强制转换来解决此问题:Box::new(move || assert!(ex > 0)) as Box<FnOnce()>

  4. 此时,代码将进行编译,但由于语言限制,当您添加对装箱FnOnce()的调用时,将收到编译错误。见"cannot move a value of type FnOnce" when moving a boxed function。在每晚Rust上,您可以将FnOnce更改为FnBox。否则,您可以使用FnMut代替或使用该问题的解决方法之一。还有另一种解决方法是基于定义in the Rust book给出的额外特征(参见清单20-20和清单20-21之间的部分)。

  5. 以下是使用FnBox的固定代码:

    #![feature(fnbox)]
    use std::boxed::FnBox;
    
    fn run_all_tests<I>(tests: I)
    where
        I: IntoIterator<Item = Box<FnBox()>>,
    {
        for t in tests {
            t();
        }
    }
    
    fn main() {
        let examples = [1, 2, 3];
    
        run_all_tests(examples.iter().map(|&ex| {
            Box::new(move || assert!(ex > 0)) as Box<FnBox()>
        }));
    }