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

Why doesn't my generic From implementation accept Box<Fn()> values?

考虑以下简单结构,该结构的 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()
}

这会打印出 "Hello, world!":

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 成功的地方失败?

我不确定,但我怀疑这不会发生,因为需要两次转换,而且跳转次数太多。再次查看错误信息:

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());

why isn't the as Box<Fn()> needed in my first example using the Foo function?

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

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

  1. Box<closure>Box<Fn()>。这是由编译器提供的 / as 关键字。
  2. TFoo<T>。这是由 From 实现提供的。

您可以执行第一步(或隐式完成),或者您可以通过 .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!"));
}

你也可以接受泛型:

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

fn main() {
    callg(Box::new(|| println!("Hello, world!")).into());
}

之所以可行,是因为我们不再转换为 Box<Fn()>,因此只需要一个转换步骤。