对自定义FnBox代码的轻微调整会阻止它编译

Mic*_*son 7 rust

以下代码在每晚1.7.0编译时没有警告:

trait FnBox {
    fn call_box(self: Box<Self>);
}

impl <F: FnOnce()> FnBox for F {
    fn call_box(self: Box<F>) {
        (*self)()
    }
}

fn main() {}
Run Code Online (Sandbox Code Playgroud)

但是当我进行这种轻微修改时,我认为这意味着完全相同的事情,我得到一个关于FnOnce未经确定而且不能移动的错误.

trait FnBox {
    fn call_box(self: Box<Self>);
}

impl FnBox for FnOnce() {
    fn call_box(self: Box<FnOnce()>) {
        (*self)();
    }
}

fn main() {}
Run Code Online (Sandbox Code Playgroud)

错误信息:

error[E0161]: cannot move a value of type dyn std::ops::FnOnce(): the size of dyn std::ops::FnOnce() cannot be statically determined
 --> src/main.rs:7:9
  |
7 |         (*self)();
  |         ^^^^^^^
Run Code Online (Sandbox Code Playgroud)

这两个例子有什么区别,为什么第一个例子没有问题?

Vla*_*eev 9

实际上,有很大的不同.在第一段代码中:

impl<F: FnOnce()> FnBox for F {
    fn call_box(self: Box<F>) {
        (*self)()
    }
}
Run Code Online (Sandbox Code Playgroud)

声明对于任何F实现FnOnce我们实现的类型FnBox.F是一种具体类型,并且在每个调用站点call_box()方法将被单态化.F每个调用站点的具体类型及其大小都是编译器已知的,因此该定义没有问题.

但是,在第二段代码中:

impl FnBox for FnOnce() {
    fn call_box(self: Box<FnOnce()>) {
        (*self)();
    }
}
Run Code Online (Sandbox Code Playgroud)

声明裸特征对象类型实现FnBox.但是,这个实现是不合理的:虽然Box<FnOnce()>是一个正确的,大小适合变量和函数参数的FnOnce()类型,但它本身不是 - 它是一个简单的特征对象类型而且它是未定义的,也就是说,它的大小是不知道的.编译器.这对您使用此类型可以执行的操作设置了一些限制,其中一个主要限制是您不能按值使用此类型的值.但是,这正是此代码中发生的情况:您尝试取消引用Box<FnOnce()>以获取FnOnce().

以前的按值self方法意味着特征不是对象安全的,并且因为FnOnce::call_once按值使用实现实例,所以它不是对象安全的.但是,self自从RFC 817实现以来,按值方法不会使特征对象不安全.self但是,根据上述推理,仍然不能在特征对象上调用按值方法.