Search code examples
rusttraitsownershiptrait-objects

impl trait with move method for trait object of same trait


I am trying to implement a trait on a boxed trait object of the same trait. I've done this before for trait whose methods take &self which works fine, but not self.

// The purpose of this trait is to allow for converting any kind of "group" which might eg be nested tuples like below, and convert it into a flat Vec of Items
trait Group {
    fn into_vec(self) -> Vec<Box<dyn Item>>;
}

trait Item: Group {}

// Foo is an Item an can also be made into a group
struct Foo {}
impl Item for Foo {}
impl Group for Foo {
    fn into_vec(self) -> Vec<Box<dyn Item>> {
        vec![Box::new(self)]
    }
}

// impl Group for data structures which contain items or other nested structures containing items
impl<A: Group, B: Group> Group for (A, B) {
    fn into_vec(self) -> Vec<Box<dyn Item>> {
        let mut new_vec = Vec::new();
        new_vec.extend(self.0.into_vec().into_iter());
        new_vec.extend(self.1.into_vec().into_iter());
        new_vec
    }
}

// Can create a single group fine
fn get_group() -> impl Group {
    (Foo {}, (Foo {}, Foo {}))
}

// Sometimes I might want to return different groups from different braches so need to box them
// However I'm not sure how to implement this. self.into_vec() is an ifinite recursion, and can't deref self either.
impl Group for Box<dyn Group> {
    fn into_vec(self) -> Vec<Box<dyn Item>> {
        (*self).into_vec()
    }
}
fn get_group_conditonal(condition: bool) -> impl Group {
    if condition {
        Box::new((Foo {}, (Foo {}, Foo {}))) as Box<dyn Group>
    } else {
        Box::new(Foo {}) as Box<dyn Group>
    }
}

I realise that in this particular example I could change fn get_*() functions to return Box<dyn Group> to solve the problem. However, the rest of the API has functions which take inputs as impl Group. If I can't impl Group for Box<dyn Group> then this would carry into the rest of the API and require that all functions only take boxed trait objects as inputs, rather than impl Group, and I want to avoid this if possible.


Solution

  • The only solution I can come up with and that's commonly recommended is to have the trait method take Box<Self> instead of Self:

    trait Group {
        fn into_vec(self: Box<Self>) -> Vec<Box<dyn Item>>;
    }
    

    then

    impl Group for Box<dyn Group> {
        fn into_vec(self: Box<Self>) -> Vec<Box<dyn Item>> {
            (*self).into_vec()
        }
    }
    

    works as you can see on the Playground because it doesn't have to deal with a raw dyn Group