Accepting T: SomeTrait or Box<dyn SomeTrait + 'static>

I'm trying to hold a Box, but unburden the caller from caring whether they need to pass a T: Something or a Box<dyn Something> or a Box<T: Something>.

Basically in this code, I would like to make let why_not = Greeter::new(Box::new(Hello)); also compile.

Can anyone point me in a direction for this?

trait Greet {
    fn greet(&self) -> &'static str;
}

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

struct Hello;
impl Greet for Hello {
    fn greet(&self) -> &'static str {
        "hello"
    }
}

struct Greeter {
    inner: Box<dyn Greet>,
}

impl Greeter {
    // Can this function take either a `T: Greet` or a `Box<dyn Greet + 'static`?
    fn new<T: Into<Box<dyn Greet>>>(greet: T) -> Self {
        Self {
            inner: greet.into(),
        }
    }

    fn print(&self) {
        println!("{}", self.inner.greet())
    }
}

fn main() {
    let greeter = Greeter::new(Hello);
    let why_not = Greeter::new(Box::new(Hello));
    greeter.print();
    why_not.print();
}
36 |     let why_not = Greeter::new(Box::new(Hello));
   |                                ^^^^^^^^^^^^^^^
   |                                |
   |                                expected an implementor of trait `From<Box<Hello>>`
   |                                help: consider borrowing here: `&Box::new(Hello)`

Your are missing the fact that when T : ?Sized + Greet, you don't automagically have Box<T> : Greet (including, although unrelated here, that Box<dyn Trait> does not impl Trait, even though dyn Trait does. In practice, since Box : Deref, you can call methods from the inner value on a boxed thing, since the . operator can implicitly dereference, but that fact is just syntactic sugar: we don't get a magical impl on it (the most known counter example would be that Box<impl Future> is not always Future, an added Unpin bound is required for that to work)).

But in most cases, explicitely adding such an impl is trivial, and does "solve" this kind of issues:

impl<T : Greet> Greet for Box<T> {
    fn greet (self: &'_ Box<T>) -> &'static str
    {
        #![deny(unconditional_recursion)] // to prevent mistakes
        (&**self).greet()
        // or `T::greet(self)`
    }
}
  • This way, impl<T: Greet + 'static> From<T> for Box<dyn Greet> can kick in for the case where type T = Box<impl Greet>, such as type T = Box<Hello>.

  • This, indeed, makes your code compile (playground).


That being said, this does not do exactly what you want:

impl<T : Greet> Greet for Box<T> …

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

Let's consider your Box::new(Hello) example:

  1. type T = Box<Hello>,

  2. fn from(g: Box<Hello>) -> Box<dyn Greet> {
        Box::new(g) // Box<Box<Hello>>
        /* as Box<dyn Greet> */
    }
    

So, as you can see, we are double-boxing our Box<Hello> :grimacing:

2 Likes

Thank you @Yandros, this helps guide my thinking. Doesn't seem worth it to try to provide this convenience for the user.