Method call resolution for `Box`ed type

2020-05-01 06:54发布

Rust playground:

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

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

fn main() {
    let job: Box<FnOnce()> = Box::new(|| {});
    // versions that compile
    // let job = Box::new(|| {});
    // let job: Box<FnBox> = Box::new(|| {});
    job.call_box();
}

job.call_box(); doesn't compile:

error[E0599]: no method named `call_box` found for type `std::boxed::Box<std::ops::FnOnce()>` in the current scope
  --> src/main.rs:16:9
   |
16 |     job.call_box();
   |         ^^^^^^^^
   |
   = note: job is a function, perhaps you wish to call it
   = note: the method `call_box` exists but the following trait bounds were not satisfied:
           `std::ops::FnOnce() : FnBox`
   = help: items from traits can only be used if the trait is implemented and in scope
   = note: the following trait defines an item `call_box`, perhaps you need to implement it:
           candidate #1: `FnBox`

Why doesn't the compiler see the conversion to FnBox here?

I suppose when I use let job = Box::new(|| {}); that results in a Box<closure> type. The compiler manages to resolve job.call_box(), which is Box<closure> -> Box<FnOnce()> -> Box<FnBox> (because FnBox is implemented for FnOnce(), not for closure). Why doesn't just Box<FnOnce()> -> Box<FnBox> work?

标签: rust
1条回答
Lonely孤独者°
2楼-- · 2020-05-01 07:49

Author's note: Since Rust 1.35, an adapter trait like FnBox is not required to call boxed FnOnce closures; read to the end to see why.

FnBox isn't implemented for FnOnce().

FnOnce() has two meanings: as a trait, and as a type. In Box<FnOnce()>, it's a (dynamic, unsized) type. Since Rust 1.27, the type can be written more explicitly as dyn FnOnce(). For the rest of this answer I will use dyn in order to make clear when I'm talking about the trait and when about the dynamic type.

The impl you wrote doesn't implement FnBox for the type dyn FnOnce(), because generic parameters have an implicit Sized bound. If we follow the advice of that question's answer and add + ?Sized to F, the compiler does give a slightly more helpful error message:

impl<F: FnOnce() + ?Sized> FnBox for F {
    fn call_box(self: Box<F>) {
        (*self)()
    }
}
error[E0161]: cannot move a value of type F: the size of F cannot be statically determined
 --> src/main.rs:7:9
  |
7 |         (*self)()
  |         ^^^^^^^

Which does explicitly call out the place where Sizedness is required.

In versions of Rust before 1.35, there was no way to fix this error; trait objects of FnOnce() were just useless. However, today Box<dyn FnOnce()> implements FnOnce(), so you can call it like a normal closure, without using *:

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

See also

查看更多
登录 后发表回答