Trait Objects: No Generics For You!

I just came across the limitation that trait objects cannot have methods with type parameters.

Coming from JVM languages, this is a really harsh limitation.

Are there "workarounds" I'm possibly missing?

(Context: My Services are trait objects. They need dynamic dispatch because, for one thing, for DI you need to be able to use test impls as well. And I want/need to have methods that are generic over certain types.)

Since you're already using trait objects, you could use even more trait objects to work around it:

trait A {
    fn f(&self, b: &dyn B) {...}
}

Generic methods can't can't be virtual because they describe an unbounded set. e.g.

trait C {
    fn f<T: B>(&self, b: &T) {...}
}

The compiler would need to create a vtable with f<T1>, f<T2>, ... for each type Tn.

I wouldn't use trait objects for either DI or genericity. They're too limiting for that, and you likely don't need the things they do offer.

For DI, I'd generally use a normal type parameter. That is, supposing I have a type Mailer which has a dependency that requires an implementation of Transport, I would generally default to impl<T> Mailer<T> where T: Transport, using T for the type of the transport, rather than impl Mailer, using &dyn Transport or Box<dyn Transport> for the transport.

Your tests would run against, say, Mailer<InMemory>, while the running application might use Mailer<Smtp>.

The one place trait objects are truly hard to avoid (and usually worthwhile) is in heterogeneous collections of values implementing a common trait, where there isn't necessarily any single type (other than the trait object type) that you could be using.

Can you give a more concrete example of how your code uses trait objects?

2 Likes

This would be very heavyweight for simply abstracting over numeric types (int & float...).

In Rust, yes. No other language I know of implements parameterized type functions with duplication, so creating vtables for them is not a problem.

Any language which doesn't do that does essentially the "even more trait objects" thing, under the hood.

4 Likes

For example, in JVM languages - where our interlocutor is coming from - every non-primitive value is also a trait object, for every class and interface the value implements.

4 Likes

C++ also duplicates. Languages which sneak in runtime magic don't have to duplicate.

2 Likes

All true, of course. Twas not a criticism.

For this usecase I would wrap them in an enum and implement numerical traits for it.

4 Likes