Traits as generic type parameters


#1

Hello! First time poster here, I hope this isn’t off-topic. Someone on #rust-beginners said I should post this here. :slight_smile:

To refresh my understanding of how dynamic dispatch works in Rust, I wrote some sample code roughly:

let mut my_vec: Vec<Box<MyTrait>> = Vec::new()
my_vec.push(Box::new(Foo{}));
my_vec.push(Box::new(Bar{}));
my_vec.push(Box::new(Baz{}));

Seeing all that repeated Box::new(…) boilerplate, I thought hey, I’ll just write a trait that’ll do that for me so that I can push things that implement a trait into a Vec<Box<ThatTrait>>. That lead to the following conversation:

23:54:08    NfNitLoop | Can generic parameters not refer to traits? I'm trying to implement a method for
                      | Vec<Box<SomeTrait>> that will accept T:SomeTrait, but it doesn't like using SomeTrait
                      | in the where clause.
23:54:10    NfNitLoop | https://is.gd/jpI5iV
23:55:50     scottmcm | NfNitLoop: there are only generic type parameters, not generic trait parameters
23:56:22     scottmcm | NfNitLoop: so in `impl<Trait, T>` Trait is a type, not a trait, and thus it can't be
                      | used in a bound
23:57:04       robrob | this works: https://is.gd/AV0wj3 but not sure what you intended
23:57:50    NfNitLoop | Yeah, I don't want it bound to a single trait, though. 
23:58:15    NfNitLoop | I want to say, for all Vec<Box<Foo>> (where Foo is a trait), let me push(T) where T
                      | implements trait Foo. 
23:59:33       robrob | i think this only works for static traits
23:59:43    NfNitLoop | What's a static trait? 
23:59:52            * | NfNitLoop googles.
23:59:59     scottmcm | NfNitLoop: https://is.gd/WXaBFB 
00:00:07       robrob | a trait known at compile-time I mean
00:01:04    NfNitLoop | scottmcm: aha. that works.
00:01:31     scottmcm | NfNitLoop: it doesn't require heterogeneity, though
[...]
00:02:30    NfNitLoop | scottmcm: doesn't *require* it, but it allows it, no? 
00:02:36     scottmcm | NfNitLoop: I'm still checking that :P
00:04:03    NfNitLoop | Oh, no, it doesn't. 
00:04:14     scottmcm | Yeah, into isn't as magical as I wanted.
00:04:15    NfNitLoop | It just pushes the problem one further down the chain.
00:04:18     scottmcm | Time to try something else...
[...]
00:17:10     scottmcm | NfNitLoop: hmm, I can't seem to find the constraint that will let me turn Box<T> into
                      | Box<Trait>
00:17:25     scottmcm | If they're concrete it's a coersion, but I don't know what the method should be.
00:19:34     scottmcm | NfNitLoop: https://is.gd/imbYaQ <-- that works, but it's odd that I needed to impl   
                      | From myself
00:24:04        bluss | scottmcm: T: Trait is all that's needed (and it coerces)
00:24:21        bluss | scottmcm: I guess it only got harder to use .into()
00:24:47     scottmcm | bluss: Trait is a generic parameter, though, so it's not usable as a bound?
00:25:04        bluss | scottmcm: oh that's a lot more interesting. You're right
00:26:30     scottmcm | bluss: yeah, that's why it got weird.  There isn't a trait to bound to for that
                      | coersion, right?
00:29:03    NfNitLoop | scottmcm: Yeah, having to implement From is just pushing the need to do an impl per
                      | trait down another level. I'd love to avoid that if possible. :) 
00:29:52     scottmcm | NfNitLoop: yeah, the updated one just has a simpler impl than the last one :|
00:32:05     scottmcm | But this new requirement is something that might be reasonable to have std provide,
                      | since most coersions are available via Into.  Maybe start an IRLO thread?

The code samples we were discussing should still be live at those rust playground links.

Is there a way to do this currently without resorting to writing an impl for every trait? Should there be?

Thanks!


#2

The problem, as I see it, was that there’s a coersion for Box<T> -> Box<U> where T:U, but there’s no trait or method that lets that be used in generics. (Generic trait parameters would be awesome, but a huge feature, so I wouldn’t want to propose those today.)

Most coersions are available via Into; would it make sense for this Box conversion to be available there? Is there a way it can be implemented in std without needing compiler magic?

Basically, every trait needs this:

impl<T:Trait+'static> From<Box<T>> for Box<Trait> {
    fn from(x: Box<T>) -> Box<Trait> {
        x // it's a coersion, so do nothing
    }
}

#3

CoerceUnsized is probably what you’re after. Personally I don’t want to stabilize it before generalizing it to all coercions.


#4

Ah, that did the trick. Thanks!

The docs for CoerceUnsized were a bit confusing. It took a while to figure out, but the result is nice and simple:

trait PushHeterogeneous<T>
{
    /// Push an item that implements a trait into a heterogeneous list of trait objects.
    fn pushh(&mut self, item: T);
}

impl<Trait, T> PushHeterogeneous<T> for Vec<Box<Trait>>
where T: Unsize<Trait> + 'static,  Trait: ?Sized
{
    fn pushh(&mut self, item: T) {
        // This doesn't work:
        // self.push(Box::new(item));

        // Need to give a bit more type hinting:
        self.push(Box::<T>::new(item));
    }
}