Coerce to trait object inside generic function

Is it possible to coerce to a trait object inside a generic function? I know the CoerceUnsized trait on nightly but I need something that works in stable. I wanted something like this, but this isn't legal code for many reasons:

fn box_and_coerce<Type, Trait>(obj: Type) where Type: Trait {
    let box_dyn = Box::new(obj) as Box<Trait>;
    // Do stuff with box_dyn
}

box_and_coerce::<i32, dyn Display>(123);

The syntax where Type: Trait is not valid because the right-hand-side must be a trait, but it is a type in your case. I know you called it Trait, but it is a type here. Note in particular that dyn SomeTrait is a specific type called the trait object associated with the trait, and the trait object type is separate from the trait itself.

Anyway, you're going to need some trait that allows the operation. If you don't have access to CoerceUnsized, then you can make your own similar trait. For example:

use std::fmt::Display;

trait CanUnsize<Target: ?Sized> {
    fn unsize_box(self: Box<Self>) -> Box<Target>;
}

impl<'a, T> CanUnsize<dyn Display + 'a> for T
where
    T: Display + 'a,
{
    fn unsize_box(self: Box<T>) -> Box<dyn Display + 'a> {
        self
    }
}

You cannot auto-implement this trait for all trait objects without the use of CoerceUnsized.

2 Likes

Without CoerceUnsized, you can't be generic over multiple traits. You can, however, coerce a generic type into a specific trait object:

use std::fmt::Display;

fn box_and_coerce<'a, Type>(obj: Type) where Type: Display + 'a {
    let box_dyn = Box::new(obj) as Box<dyn 'a + Display>;
    // Do stuff with box_dyn
}

#[test]
fn test() {
    box_and_coerce(123);
}

Note also that this:

...is highly problematic by itself. Without knowing statically what the trait in question is, the only things you can do is probably to store the box_dyn somewhere as Box<dyn Any> or something like that.

1 Like

Yes, but it's a trait and not any trait. You can't coerce to any trait, ie. you can't be generic over the trait itself. You have to supply a concrete trait and put it on your function as a bound.

Furthermore, dyn Trait is not a trait. It's a type. It means "this type is statically unknown but it was created from something that implements Trait so you can call the corresponding methods on it".

Thank you for explaining. You even anticipated my follow-up question and preemptively crushed my dreams :D! But it's good to know. Since a blanket impl isn't possible, probably I will create a macro for implementing CanUnsize. It seems like the best remaining path.

That's almost exactly what I was going to do, insert it in an AnyMap as type Box<dyn Trait> (which is internally stored as Box<Box<dyn Any>>)

Then you shouldn't perform two rounds of conversion; it's unnecessary. You should only put an Any or 'static bound on your type instead:

fn box_and_coerce<T: 'static>(obj: T) {
    let box_dyn: Box<dyn Any> = Box::new(obj);
    // Do stuff with box_dyn
}

And, at this point, the box_and_coerce function itself is unnecessary, too – you just write a function that performs the rest of the tasks (as per the comment).

After adding, I need to get it back using the trait object instead of the concrete type:

trait T { fn t_func(); }

struct S;
impl T for S { ... }

anymap.insert::<Box<dyn T>>(Box::new(S));
// later
anymap.get::<Box<dyn T>>()?.t_func();

I would love to not box it twice, but due to this requirement I don't know if that can be avoided. I would have to pass a dyn T into AnyMap::insert before it boxes internally, but you can't pass a raw unsized type by value.

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.