impl trait with move method for trait object of same trait

63 Views Asked by At

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.

2

There are 2 best solutions below

1
cafce25 On BEST ANSWER

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

0
Chayim Friedman On

If you don't want to pay the cost of boxing for each call, you can have two methods:

// 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>>;
    fn into_vec_dyn(self: Box<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)]
    }
    
    fn into_vec_dyn(self: Box<Self>) -> Vec<Box<dyn Item>> {
        self.into_vec()
    }
}

// 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
    }
    
    fn into_vec_dyn(self: Box<Self>) -> Vec<Box<dyn Item>> {
        self.into_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>> {
        <dyn Group>::into_vec_dyn(self)
    }
    
    fn into_vec_dyn(self: Box<Self>) -> Vec<Box<dyn Item>> {
        self.into_vec()
    }
}

Of course, writing the method out explicitly for each implementation is... not nice, but we can solve that by splitting the trait:

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

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

// 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: GroupIntoVecDyn {
    fn into_vec(self) -> Vec<Box<dyn Item>>;
}

Because GroupIntoVecDyn is a supertrait of Group, you can call its method (even on dyn Group), and because of the blanket implementation, you don't actually have to implement it.