Generics: is it possible to make a function that takes either impl T or Box<dyn T>?

I have a container type that manage a list of dynamic children:

pub trait Child {
}

pub struct Container {
    children: Vec<Box<dyn Child>>
}

I want people to be able to add children to this container. As an API convenience, I think it would be nice if there could just be one method to add things to the container that automatically wraps its argument in a Box if needed. Currently it seems like I need to have 2 different methods, like

impl Container {
    fn push_child(&mut self, child: impl Child + 'static) {
        self.push_child_box(Box::new(child));
    }

    fn push_child_box(&mut self, child: Box<dyn Child>) {
        self.children.push(child)
    }
}

I thought I could maybe do something like automatically implementing my trait for all Boxed types, but doing

impl<T> Child for Box<T> {}

Still just gives the error "the size for values of type dyn Child cannot be known at compilation time" if you try to call push_child with a boxed value. Is there a way I can define this API in a generic way so that users can only interact with a single method?

1 Like

dyn Trait already automatically implements Trait. Additionally, you can also implement the trait for Box<dyn Trait> manually.

The given blanket impl could also work if you add a T: ?Sized + Child bound.

2 Likes

For context,

  • Most type parameter declarations have an implied : Sized bound
  • dyn Trait is not Sized
  • : ?Sized disables the implied bound

But you probably just need impl Child for Box<dyn Child> [1].


  1. and maybe some others like Arc<dyn Child> or such depending on the use case ↩ī¸Ž

3 Likes

I would give the full blanket impl if possible, that way you get Box<dyn Child + Send + Sync> for free, and also if Child has other sub-traits then Box<dyn Foo>: Child where trait Foo: Child will also work.

impl<T: ?Sized + Child> Child for Box<T> {...}
7 Likes

Thanks! Am I correct in thinking that this will work, but has the downside of sometimes creating Box<Box<dyn Child>> instances?

Well, yes. If you pass Box<dyn Child> to push_child then it will double box. This is unavoidable for now. (It might be possible to do with some restructuring, but isn't really worthwhile unless you find this in your profiles)
Edit: Just tried it out and it would require min_specialization (an unstable feature)
Playground Link

1 Like

You can avoid the double allocation by exploiting the idempotent impl<T> From<T> for T applied to Box<dyn Child> and by also adding an explicit impl<T: Child> From<T> for Box<dyn Child> (Playground):

struct Foo;

impl Child for Foo {}

impl<T: Child + 'static> From<T> for Box<dyn Child> {
    fn from(child: T) -> Self {
        Box::new(child)
    }
}

fn main() {
    let mut cont = Container::default();
    cont.push_child(Foo);
    cont.push_child(Box::new(Foo) as Box<dyn Child>);
}
2 Likes

With some helper methods, this can be done without unnecessary double boxes:

pub trait ChildHelperTrait {
    fn boxed_into_trait_object<'a>(self: Box<Self>) -> Box<dyn Child + 'a>
    where
        Self: 'a;
}
impl<T: Child> ChildHelperTrait for T {
    fn boxed_into_trait_object<'a>(self: Box<Self>) -> Box<dyn Child + 'a>
    where
        Self: 'a,
    {
        self
    }
}

pub trait Child: ChildHelperTrait {
    fn into_boxed_trait_object<'a>(self) -> Box<dyn Child + 'a>
    where
        Self: Sized + 'a,
    {
        Box::new(self).boxed_into_trait_object()
    }
    
    fn some_method(&self);
}

pub struct Container {
    children: Vec<Box<dyn Child>>,
}

impl Container {
    fn push_child(&mut self, child: impl Child + 'static) {
        self.children.push(child.into_boxed_trait_object());
    }
}

impl<T: Child + ?Sized> Child for Box<T> {
    fn into_boxed_trait_object<'a>(self) -> Box<dyn Child + 'a>
    where
        Self: 'a,
    {
        self.boxed_into_trait_object()
    }
    
    fn some_method(&self) {
        println!("called on Box ...");
        (**self).some_method();
    }
}

// demo impl, showing you don't need to manually implement any of these helper methods
struct Foo;

impl Child for Foo {
    fn some_method(&self) {
        println!("called on Foo.\n");
    }
}

fn main() {
    let mut c = Container {
        children: vec![],
    };
    
    // all these only need one layer of Box
    c.push_child(Foo);
    c.push_child(Box::new(Foo));
    c.push_child(Box::new(Foo) as Box<dyn Child>);
    // vs. manually creating two layers of Box...
    c.push_child(Box::new(Box::new(Foo)));
    
    c.children.iter().for_each(|c| c.some_method());
}

Rust Playground

4 Likes

If you do it this way, then you can't implement Child for Box<dyn Child> which is really unfortunate.

Wow, I missed this solution! Nice

1 Like

Yeah but you can still dereference it, so it's at most a minor inconvenience.

1 Like

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.