Call consuming method for dyn trait object?

I want to call a consuming method (is that what it's called? or named destructor? a method that takes self by value so it cannot be used afterwards) for a trait object. But apparently that can't be done. Can I rewrite the line marked "Error" or add some kind of adapter or something so I can make the call?

trait Trait {
    fn consume(self);
}

struct Impl;
impl Trait for Impl {
    fn consume(self) {}
}

fn main() {
    let obj = Box::new(Impl);
    obj.consume();

    let obj2: Box<dyn Trait> = Box::new(Impl);
    obj2.consume(); // Error.  Can I fix it?
}

(Playground)

I see the problem on a language level, Box<dyn T> derefs to &dyn T, but I own the box, and I can deallocate it, so T being unsized is not really a problem here, right?

1 Like

It works on nightly if you add:

#![allow(incomplete_features)]
#![feature(unsized_fn_params)]
2 Likes

So it does, thanks! That at least gives some hope for the future, even if Tracking issue for RFC #1909: Unsized Rvalues · Issue #48055 · rust-lang/rust · GitHub , that this flag apparently belongs to, seems to ha a rather big scope and probably won't be done in the near future.

Unfortunatley, I have decided earlier that the program this is part of needs to work on stable, so no nightly features for me.

1 Like

The reason it doesn't work is that dyn Trait is unsized and therefore the compiler doesn't know how to pass that into a function because it doesn't statically know how many bytes to copy onto the stack frame of consume.

You can try to work around it by adding another implementation:

impl Trait for Box<dyn Trait> {
    fn consume(self) {}
}
2 Likes

Unfortunatley, that only moves the problem to how I should write the impl of that consume. Maybe I did the example code a bit too minimal, in my actual code I have different structs implementing the trait, and they do different things in their implementation of consume.

You can use Box<Self> as the receiver type for the trait method:

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

(Playground)

5 Likes

You can actually combine the two approaches mentioned above. (The self: Box<Self> method and the implementation for Box<dyn Trait>.) I'd assume you want to keep it consume(self) because if it's not a trait object, you don't want to enforce boxes. So the straightforward approach would be add a second method

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

struct Impl;
impl Trait for Impl {
    fn consume(self) {}
    fn consume_boxed(self: Box<Self>) {}
}

fn main() {
    let obj = Box::new(Impl);
    obj.consume();

    let obj2: Box<dyn Trait> = Box::new(Impl);
    obj2.consume_boxed();
}

But that means you'll now have to call this as .consume_boxed(), which is annoying. But you can actually write the implementation for

impl Trait for Box<dyn Trait> {
    fn consume(self) {
        self.consume_boxed()
    }
    fn consume_boxed(self: Box<Self>) {
        self.consume()
    }
}

now. So the code looks like

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

struct Impl;
impl Trait for Impl {
    fn consume(self) {}
    fn consume_boxed(self: Box<Self>) {}
}

impl Trait for Box<dyn Trait> {
    fn consume(self) {
        self.consume_boxed()
    }
    fn consume_boxed(self: Box<Self>) {
        self.consume()
    }
}

fn main() {
    let obj = Box::new(Impl);
    obj.consume();

    let obj2: Box<dyn Trait> = Box::new(Impl);
    obj2.consume();
}

Still annoying that everybody needs to always manually implement consume_boxed now, though. And you cannot just do

trait Trait {
    fn consume(self);
    fn consume_boxed(self: Box<Self>) {
        self.consume();
    }
}
error[E0161]: cannot move a value of type Self: the size of Self cannot be statically determined
 --> src\main.rs:4:9
  |
4 |         self.consume();
  |         ^^^^^^^^^^^^^^

Solution: Add another trait! A supertrait works great, actually.

trait Trait: TraitBoxed {
    fn consume(self);
}

trait TraitBoxed {
    fn consume_boxed(self: Box<Self>);
}

How this helps? It allows for a generic implementation for sized types!

impl<T> TraitBoxed for T
where
    T: Trait,
{
    fn consume_boxed(self: Box<Self>) {
        self.consume()
    }
}

Full code right now...

trait Trait: TraitBoxed {
    fn consume(self);
}

trait TraitBoxed {
    fn consume_boxed(self: Box<Self>);
}

impl<T> TraitBoxed for T
where
    T: Trait,
{
    fn consume_boxed(self: Box<Self>) {
        self.consume()
    }
}

struct Impl;
impl Trait for Impl {
    fn consume(self) {
        println!("it works!")
    }
}

impl Trait for Box<dyn Trait> {
    fn consume(self) {
        self.consume_boxed()
    }
}

fn main() {
    let obj = Box::new(Impl);
    obj.consume();

    let obj2: Box<dyn Trait> = Box::new(Impl);
    obj2.consume();
}

Now, this implementation for Box<dyn Trait> is still a bit restrictive, it only works with Box<dyn Trait + 'static> right now. We can actually generalize further though anyways; we can make a generic implementation for any Box<T> when T: ?Sized + Trait....

impl<T> Trait for Box<T>
where
    T: ?Sized + Trait,
{
    fn consume(self) {
        self.consume_boxed()
    }
}

And we get a stack overflow... why? Because the

impl<T> TraitBoxed for T
where
    T: Trait,
{
    fn consume_boxed(self: Box<Self>) {
        self.consume()
    }
}

implementation now calls the implementation on Box, quite circular! We need to make the previously implicit dereferencing explicit:

impl<T> TraitBoxed for T
where
    T: Trait,
{
    fn consume_boxed(self: Box<Self>) {
        (*self).consume()
    }
}

There we go, perfect solution:

trait Trait: TraitBoxed {
    fn consume(self);
}

trait TraitBoxed {
    fn consume_boxed(self: Box<Self>);
}

impl<T> TraitBoxed for T
where
    T: Trait,
{
    fn consume_boxed(self: Box<Self>) {
        (*self).consume()
    }
}

struct Impl;
impl Trait for Impl {
    fn consume(self) {
        println!("it works!")
    }
}

impl<T> Trait for Box<T>
where
    T: ?Sized + Trait,
{
    fn consume(self) {
        self.consume_boxed()
    }
}

fn main() {
    let obj = Box::new(Impl);
    obj.consume();

    let obj2: Box<dyn Trait> = Box::new(Impl);
    obj2.consume();
}
8 Likes

Thanks @Lej77 and @steffahn ! I had just about given up and decided to just use an enum of all possible implementatons instead of a Box<dyn Trait>.

I think in my current use-case, actually just having the method consume a Box<Self> rather than a Self isn't that bad. I'll probably only call it in one place anyway. So I'm marking the @Lej77 answer as my solution, but I'll definitely keep the @steffahn possibility in mind if I need it later.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.