Trait T is not implemented for Box<dyn T>. How to solve?

Hello.

This code fails to compile with the following error:

error[E0277]: expected a `FnOnce()` closure, found `dyn CallOnce`

In the end, I want to be able to pass Box<dyn CallOnce> to a function that accepts impl CallOnce, while also having implemented <T: FnOnce> CallOnce for T.

How to fix the code?

Also a question of secondary importance, why Box<dyn T> does not automatically implement T? Would there be any problems with that?

That is exactly what I wanted to solve: for the function consume_call_once to accept a generic parameter impl CallOnce (without requiring Box)

You can add a supertrait that deals with boxes specifically...

pub trait BoxedCallOnce {
    fn boxed_call_once(self: Box<Self>);
}

pub trait CallOnce: Send + BoxedCallOnce {
    fn call_once(self);
}

...provide it for Sized implementors of the base trait...

impl<T: CallOnce> BoxedCallOnce for T {
    fn boxed_call_once(self: Box<Self>) {
        <Self as CallOnce>::call_once(*self)
    }
}

...and use that to implement the base trait for Box<dyn ..>:

// Plus `dyn CallOnce + AutoTrait` ... consider using a macro
impl CallOnce for Box<dyn CallOnce + '_> {
    fn call_once(self) {
        self.boxed_call_once()
    }
}
5 Likes

Thank you very much for the solution. It works, and I will use it.

However, I really struggle to understand how it works, and why do we need a separate BoxedCallOnce.

I noticed that I can't call call_once of a Box<dyn T> because dyn T doesn't have a known compile time size. It's interesting though that Box<dyn FnOnce()> can be called this way, but I presume dyn FnOnce() also doesn't have a known compile time size?

Yes, there would be problems:

  • Traits may involve properties of the type that don't necessarily apply to a Box wrapper, like for example bytemuck::Pod or std::marker::Unpin. Implementing those traits automatically would be unsound.
  • Traits may use the Self type in ways that it's not obvious how to “delegate”. This isn't a problem for dyn-compatible traits, but it's a general reason why constructing a delegating implementation from Box<T> to T is not possible.

The compiler has special support for Box<dyn FnOnce()>, that can't (yet) be used by other traits in this position.

2 Likes

You can just add the method to the existing trait, but you can't provide a default body, because Self may not be Sized and in unsized types cannot be moved. You can't require Sized, as that would make your trait (or that method) not dyn compatible. So if you don't use a separate trait, all implementors would have to supply the method body themselves.

By making it a separate trait, we can provide an implementation of the method for all Sized implementors instead. And by making it a supertrait, the compiler still provides the implementation for dyn CallOnce.

Either way, in the compiler implementation of the boxed_call_once method for dyn CallOnce, the compiler effectively infallibly downcasts from Box<dyn CallOnce> to Box<ImplementingType> and calls ImplementingType::boxed_call_once. (Under the hood it's passing a type erased pointer in the shape of a Box<SomethingSized> via a method in its vtable.)

You can and my playground does -- which is possible because Box<dyn CallOnce> does have a known size. You can't call call_once of a dyn CallOnce.

impl CallOnce for [u8; 100] { ... }

fn example() {
    let bx: Box<dyn CallOnce> = Box::new([0u8; 100]);
    bx.call_once();
    // We pass `bx` to `<Box<dyn CallOnce> as CallOnce>::call_once`, which
    // Passes `bx` to `<dyn CallOnce as BoxedCallOnce>::boxed_call_once`, which
    // (*) Effectively downcasts to a `new_bx: Box<[u8; 100]>` and passes
    //    `new_bx` to `<Box<[u8; 100]> as CallOnce>::boxed_call_once`, which
    // Unboxes and passes the `[u8; 100]` to `<[u8; 100] as CallOnce>::call_once`
}

The compiler provides the <dyn CallOnce as BoxedCallOnce>::boxed_call_once method, described at (*), and the rest are our own implementations.

4 Likes

Fun fact, the reason Box<dyn FnOnce()> works is because of this standard library implementation:

impl<Args: Tuple, F: FnOnce<Args> + ?Sized, A: Allocator> FnOnce<Args> for Box<F, A> {
    type Output = <F as FnOnce<Args>>::Output;

    extern "rust-call" fn call_once(self, args: Args) -> Self::Output {
        <F as FnOnce<Args>>::call_once(*self, args)
    }
}

If you read this carefully, you can see that F: ?Sized + FnOnce allows dyn FnOnce to work here. Because the standard library is special, it can just move out of the box using *self using the unstable unsized_locals feature. One interesting side effect of this is that the boxed closure is moved back to the stack for the function call.

Not just this impl is special. The fact that a function with a self parameter can even be called on an unsized type without unsatisfied trait bounds is already special and cannot be expressed without hacky workarounds in user code.

It doesn't work that way any more. (Or here is the PR.) The closure is actually left in place.

That was merged in 2020 and there's a separate unsized_fn_params now. unsized_locals is going away in the next edition, unless beta backported back in.

That's very interesting. But if the closure in left in place, I would expect this code to produce somewhat similar addresses, which it doesn't:

fn test() -> Box<dyn FnOnce()> {
    let x = 1;
    Box::new(move || {
        let y = x;
        println!("{}", (&raw const y).addr());  // 140733118751260
    })
}

fn main() {
    let closure = test();
    println!("{}", (&raw const *closure).addr()); // 107037585419024
    closure();
}

Do you have an idea what is going on there?

This part, at least in unoptimized builds,

        let y = x;
        println!("{}", (&raw const y).addr());  // 140733118751260

is certainly going to give you the address of y, a stack variable, not anything to do with the closure. However, printing the address of x still gives a similar address. From a quick look at the assembly output, I think what is probably going on is that the closure’s own code — not the Box calling logic — moves its data onto the stack first.

By the way, a shorter way to print an address is to use :p formatting on any type of pointer value:

println!("variables {:p} {:p}", &x, &y);
...
println!("closure {closure:p}");

Yea I don't know why I added the extra variable. I did not use the pointer format specifier to make sure I was not accidentally taking the address of a double reference.

Looking at the generated MIR, I don't see the closure's code moving anything back to the stack :confused:

Interesting, as far as I can tell, the address is the same as that of the box as soon as the alignment of the moved value is 16 bytes or more. Below that it generates the same MIR but the result is different, wtf