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?
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 Sized
ness 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
- What does "Box<Fn() + Send + 'static>" mean in Rust?
- Rust Trait object conversion (why you can't go from one trait object type to another)