Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Slight tweak to custom FnBox code stops it from compiling

Tags:

rust

The following code compiles without warning on nightly 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() {}

But when I make this slight modification, which I thought meant the exact same thing, I get an error about FnOnce being unsized and not movable.

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

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

fn main() {}

Error message:

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

What is the difference between these two examples, and why are there no problems with the first one?

like image 700
Michael Hewson Avatar asked Jan 20 '16 11:01

Michael Hewson


1 Answers

There is a great difference, actually. In the first piece of code:

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

it is declared that for any type F which implements FnOnce we implement FnBox. F is a concrete type, and at each call site call_box() method will be monomorphized. The concrete type of F at each call site, as well as its size, is known to the compiler, so there are no problems with this definition.

In the second piece of code, however:

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

it is declared that bare trait object type implements FnBox. However, this implementation is unsound: while Box<FnOnce()> is a correct, sized type which is suitable for variables and function arguments, FnOnce() by itself is not - it is a bare trait object type and it is unsized, that is, its size is not known to the compiler. This places several restrictions on what you can do with this type, and one of the main restrictions is that you cannot use values of this type by value. However, this is exactly what happens in this code: you attempt to dereference Box<FnOnce()> to get FnOnce().

Previously by-value self methods would meant that the trait is not object-safe, and because FnOnce::call_once consumes the implementing instance by value, it wouldn't be object-safe. However, by-value self methods do not make the trait object-unsafe since RFC 817 got implemented. By-value self methods still can't be called on a trait object according to the above reasoning, though.

like image 87
Vladimir Matveev Avatar answered Sep 21 '22 00:09

Vladimir Matveev