为什么我的通用From实现不接受Box <fn()>值?

时间:2017-01-11 01:28:43

标签: generics rust type-conversion closures traits

考虑以下简单结构,该结构的From实现,以及包含盒装函数的结构实例的函数:

struct Foo<T>(T);

impl<T> From<T> for Foo<T> {
    fn from(x: T) -> Self {
        Foo(x)
    }
}

fn call(x: Foo<Box<Fn()>>) {
    let Foo(f) = x;
    f()
}

这打印&#34; Hello,world!&#34;:

call(Foo(Box::new(|| println!("Hello, world!"))))

无法编译:

call(Box::new(|| println!("Hello, world!")).into())

给出以下错误消息:

error[E0277]: the trait bound `Foo<Box<std::ops::Fn()>>: std::convert::From<Box<[closure@src/main.rs:15:19: 15:47]>>` is not satisfied
  --> src/main.rs:15:49
   |
15 |     call(Box::new(|| println!("Hello, world!")).into())
   |                                                 ^^^^ the trait `std::convert::From<Box<[closure@src/main.rs:15:19: 15:47]>>` is not implemented for `Foo<Box<std::ops::Fn()>>`
   |
   = help: the following implementations were found:
   = help:   <Foo<T> as std::convert::From<T>>
   = note: required because of the requirements on the impl of `std::convert::Into<Foo<Box<std::ops::Fn()>>>` for `Box<[closure@src/main.rs:15:19: 15:47]>`

我无法看到From实现比Foo构造函数更严格的任何方式。为什么intoFoo成功的地方失败?

1 个答案:

答案 0 :(得分:4)

我当然不知道,但我怀疑这不会发生,因为需要进行两次转换,并且需要进行太多的转换。再次查看错误消息:

From<Box<[closure@src/main.rs:15:19: 15:47]>>

请注意,错误提到了一个闭包。在Rust中,每个闭包都是一种独特的,不可命名的类型(有时称为Voldemort type)。关闭一个Fn,但实施 Fn

要使From转换生效,起始类型必须为Box<Fn()>。我们可以看到Box<Fn()>的显式转换允许它编译:

call((Box::new(|| println!("Hello, world!")) as Box<Fn()>).into());
  

为什么我的第一个例子中使用as Box<Fn()>函数需要Foo

同样,我怀疑这是有效的,因为只有一次转换发生。编译器知道如何将Box<closure>转换为Box<Fn()> - 它只是创建boxed trait object

我把它看作一个小图。有两条边:

  1. Box<closure>Box<Fn()>。这由编译器/ as关键字提供。
  2. TFoo<T>。这是由From实施提供的。
  3. 您可以执行第一步(或隐式执行),也可以通过.into()执行第二步。但是,从一开始到最后都没有任何步骤。

    编译器尝试遍历转换图以查找具有任意步数的转换路径可能是个坏主意。还存在许多路径的可能性,导致模糊不清。

    我可能会写它,以便该函数采用泛型并进行装箱和转换本身。这样,call的客户就不需要处理这些细节:

    fn call<F>(f: F) 
        where F: Fn(),
    {
        let x: Foo<_> = Box::new(f).into();
        // I'm assuming something interesting happens
        // here before we unpack the variable again
        let Foo(f) = x;
        f()
    }
    
    fn main() {
        call(|| println!("Hello, world!"));
    }
    

    Chris Emerson points out您也可以接受通用类型:

    fn callg<T:Fn()>(x: Foo<Box<T>>) {
        let Foo(f) = x;
        f()
    }
    
    fn main() {
        callg(Box::new(|| println!("Hello, world!")).into());
    }
    

    这是有效的,因为我们不再转换为Box<Fn()>,因此只需要一个转换步骤。