Function that takes a T or Box<dyn T>

I have a function currently accepts a Box<dyn X> by move.

trait X {}
impl X for i32 {}

fn func(input: Box<dyn X>) { .. }

// called as:
func(Box::new(0));

For ergonomic reasons, I'd also like to allow it to accept concrete types that implement X by move, and have the function Box the input itself. Essentially it should allow any owned value.

// func calls Box::new internally.
func(0);

I've gotten what I want by applying a blanket impl on Box, which then allows using Into for the input:

impl<T: 'static> From<T> for Box<dyn X> where T: X {
    fn from(v: T) -> Box<dyn X> {
        return Box::new(v);
    }
}
fn func<T: Into<Box<dyn X>>>(_input: T) { .. }

func(0);  // works!
func(Box::new(0)); // works!

But the fact that this blanket impl doesn't exist makes me wonder if there's a better way. What's the best way to have a function accept any owned trait object or concrete type, like this?

The general implementation can't exist non-magically, because you can't express a generic trait bound.

impl<T> From<T> for Box<dyn ???> where T: ??? { /* ... */ }

(And it's impossible to backwards compatibly add such a magical implementation as it would conflict with, say, your example. At least, not without being even more complicated, aka surprising.)


You need the 'static bound because Box<dyn X> is short for Box<dyn X + 'static>. But alternatively, the implementation could be more generic:

impl<'a, T: 'a> From<T> for Box<dyn X + 'a> where T: X {
    fn from(v: T) -> Box<dyn X + 'a> {
        return Box::new(v);
    }
}

However, either way, I note that inference doesn't quite do the job with your boxed version and Into bound:

    func(0);  // works!
    func::<Box<dyn X>>(Box::new(0)); // works!

    func(Box::new(0));
    // error[E0277]: the trait bound `Box<{integer}>: From<Box<{integer}>>` is not satisfied

Additionally, sometimes avoiding generics (monomorphization) is a design consideration when using dyn Trait. You may want to consider just leaving your function signature alone, but still adding the From implementation:

fn func(_input: Box<dyn X>) {  }

fn main() {
    func(0.into());  // works!
    func(Box::new(0)); // works!
}

This hits some middle ground of ergonomics for programmers calling func (they don't have to box but they do have to .into()). However, it also makes them more aware of the costs and avoids monomorphizing func just for the call to into(). It also avoids the inference shortfall from before, as the parameter type supplies a more concrete context.


Finally, if you don't care about the box or avoiding monomorphization per se, and just want an owned object that that implements the trait, you can just be generic over T: X + 'static. (But I assume this isn't the case.)

7 Likes

Note that the main reason type inference complains is that Box<impl X /* + Sized */> does not, given the reduced example, impl X itself, which means Rust doesn't know how to get a Box<dyn X> From it.

So technically a separate

impl<'lt, impl_X : 'lt + X> From<Box<impl_X>> for Box<dyn X + 'lt>

would be needed, but then we'd overlap with somebody doing impl X for Box<MyType>.


So, to me, the solution there is to add the missing link / step in this design, which is to say that X should be Box transitive (note that this doesn't make sense for all traits: e.g., Box<impl Copy> can't be Copy itself!).

From there, we get everything to Just Work™, but for the sad aspect of Box<impl X> becoming a Box<dyn X> by virtue of being an impl X itself, and thus being Boxed again.

We can solve this last point by offering a default way for an impl X to be Boxed, and overriding it in the Box-transitive step:

  trait X  {
      …

+     fn into_box_dyn<'slf> (
+         it: Self,
+     ) -> Box<dyn X + 'slf>
+     where
+         Self : 'slf + Sized,
+     {
+         Box::new(it)
+     }
  }
  
+ impl<T : X> X for Box<T> {
+     …
+
+     fn into_box_dyn<'slf> (
+         it: Box<T>,
+     ) -> Box<dyn X + 'slf>
+     where
+         Self : 'slf,
+     {
+         it
+     }
+ }
  
  impl<'lt, impl_X: 'lt + X> From<impl_X> for Box<dyn X + 'lt> {
      fn from(v: impl_X) -> Box<dyn X + 'lt> {
-         Box::new(v)
+         impl_X::into_box_dyn(v)
      }
  }
6 Likes

You can do something like this (apologies for the terseness; I’m on mobile):

impl<T: X+?Sized> X for Box<T> {
    // …
}

fn func<T: X>(_input: T) { .. }

func(0);  // works!
func(Box::new(0)); // works!

Edit: This is essentially the same as @Yandros’ solution

2 Likes

Thanks for the solutions, everyone! It sounds like at least there isn't a simpler way to go about this, which is good to hear.

In this case, yeah, I'm trying to avoid an explicit into() call, because the function is used a lot, and all of those calls quickly add up.

Great catch. It seems that also casting the Box works as well. In my case, it's much less common for this function to be called with some concrete Box<T> where T: X. So it's reasonable to accept a bit of casting in those cases. The unboxed input is the more common.

Interesting! It makes me a bit uneasy, because I'm not sure whether a Box<X> in my case is truly synonymous with an X. (I like the simplicity of knowing more concretely what type I have if I have a T: X. The double-boxing / workarounds is also a bit unfortunate.

Still, it's the only solution that does offer full ergonomics, which is impressive! :smiley:

I've ended up with this code as final, for anyone that comes by later. In most of my cases, the Box being passed in is already a Box<dyn X> so the cast ends up being rare.

trait X {}
impl X for i32 {}

impl<'a, T: 'a> From<T> for Box<dyn X + 'a> where T: X {
    fn from(v: T) -> Box<dyn X + 'a> {
        return Box::new(v);
    }
}

fn main() {
    let x_box: Box<dyn X> = Box::new(0);
    let broken_box: Box<i32> = Box::new(1);
    
    func(0); // works!
    func(x_box); // works!
    
    func(broken_box); // fails
    func(broken_box as Box<dyn X>); // works!
    
}

fn func<T: Into<Box<dyn X>>>(_input: T) {
}

If the monomorphization is a concern, you could also

#[inline(always)]
pub fn func<T: Into<Box<dyn X>>>(input: T) {
    _func(input.into())
}

fn _func(_input: Box<dyn X>) { /* ... */ }

(But, it might just not be a concern.)

1 Like

Can you elaborate? Does this lower the number of instantiations of the function?

It seems like it would still have to monomorphize, before func was inlined, but I don't understand that process well. Or perhaps there's another reason you'd use this?

If the function gets inlined, it doesn't get called. The body still has to be monomorphized in a compiler sense, but the monomorphized function won't get put into your binary if it's never called.

// Before inlining
fn f() {
    func(0);   // calls monomorphized func::<i32>
    func("");  // calls monomorphized func::<"">
    func(0.0); // calls monomorphized func::<f64>
    func(());  // calls monomorphized func::<()>
}

// After inlining
fn f() {
    _func(0.into())   // calls non-generic _func
    _func("".into())  // calls non-generic _func
    _func(0.0.into()) // calls non-generic _func
    _func(().into())  // calls non-generic _func
}

Anyway, it's a micro-optimization, and may even be a no-op if the original function was already being inlined, say. But you can explore the idea here if you want. Note how in f, all the boxing is inline and there is no compiled func. In contrast there are four gunks and g is just function calls.

3 Likes

Ahhh, I see, thanks. I don't think it's a concern for me, but useful to know!

// trait, I don't know how to make a trait "object safety".
// this ugly impl is what I could do now.
trait X{
    fn y(&self);
    fn z(&mut self);
}
// impl the trait for Box<dyn Itself>
impl X for Box<dyn X>{
    fn y(&self){
        <Self as std::ops::Deref>::deref(self).y()
    }
    fn z(&mut self){
        <Self as std::ops::DerefMut>::deref_mut(self).z()
    }
}
// thus what we could do is easy now: impl the trait for the type we want.
impl X for i32{
    fn y(&self){// this is actually the `func` we need.
        println!("{}",self);
    }
    fn z(&mut self){// this is actually the `func` we need.
        println!("{}",self);
    }
}
const func_borrow: fn(&i32)=<i32 as X>::y;
const func_mut: fn(&mut i32)=<i32 as X>::z;

fn main(){
    0.y();
    0.z();
    Box::new(1).y();
    Box::new(1).z();// all the code works, this might be better since you won't treat them differently by writting an extra & or &mut
    func_borrow(&2);// if you think a `func(...)` is better than `.func()`
    func_borrow(&Box::new(2));
    func_mut(&mut 3);
    func_mut(&mut Box::new(3));
}

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.