Consuming Box<dyn Trait>

In Rust Box<dyn FnOnce> seems to have a superpower: you can call the function and consume the box. But traits with methods with Box<Self> receivers are not object-safe. I know there's a way around this: you can redesign your API to accept &mut self instead, wrap the fields you want to consume in Option , and take them out. This is a general technique that weakens FnOnce to FnMut. But is there a way to make Box<dyn Trait> objects with a method that consumes self?

But they are!


trait Trait {
    fn method(self: Box<Self>);
}

impl Trait for Struct {
    fn method(self: Box<Self>) {
        let inner: String = self.inner;
        println!("owning, and dropping {inner:?}");
        drop(inner);
    }
}

struct Struct {
    inner: String,
}

fn main() {
    let x = Box::new(Struct {
        inner: String::from("Hello World"),
    });
    let x: Box<dyn Trait> = x;
    println!("before method call");
    x.method();
    println!("after method call");
}

(playground)

before method call
owning, and dropping "Hello World"
after method call
1 Like

They are, and you can impl Trait for Box<dyn Trait + '_> when there's a self receiver, too.

2 Likes

Thanks for the explanation. Is it necessary to name the receiver self?

If you want the method to be dyn dispatchable, yes.

It’s still true of course, that there is a superpower at play here. I’m not sure if there’s any real reason to doing it the magic way beyond the convenience – as well as possibly the shorter vtable (and the box deallocation not being behind the function call indirection could be of relevance to performance, too[1]).


  1. I’d be curious though if anyone ever properly compared the pros&cons here ↩︎

Yes: in general, if there is no self, then the parameter is not a receiver and the function is not a method; and dyn dispatch only works on methods.

It seems very weird that the blanket impl of BoxedBye is allowed; it seems circular. Was this an unintended consequence of the implementation of rustc?

How so? Do you mean because it's a supertrait of Trait, but the where clause says T: Trait? If so, that's fine so long as you don't create a cycle for the trait solver.

1 Like