Is this a good reason to use trait objects over generics?

I've always defaulted to using generics instead of trait objects unless I truly need a trait object for something. However, some of my other coworkers lean more toward trait objects because you don't need to thread generic params everywhere in your application.

Does that seem reasonable? Or should I push back for some reason?

Sometimes that can be a reasonable tradeoff to make.
The truth is, it depends on the situation. If the overhead of a vtable and dynamic dispatch is acceptable in terms of performance, then that can be worthwhile.

However, personally I almost never do that simply because trait objects are quite restrictive in the trait features they allow e.g. methods with generics, GATs, and trait MyTrait: Self all disqualify a value V of type T: MyTrait from being used as a trait object.

2 Likes

Trait objects are more limited than generics, so I wouldn't use generics just to avoid type params as a general rule. @quinedot describes the tradeoffs.

2 Likes

Yep, I know they are more limited. My question is more like - if you don't care about those limitations and the code is functionally working for you, then is it okay to use trait objects only to avoid type params everywhere?

Sure, if you're aware of the trade-offs, then of course it is up to you.

1 Like

Use of dyn Trait is quite reasonable. When the same code is used with multiple types, it can avoid code duplication, and reduce binary size.

Generics are only necessary if you either need the more complex features like associated types, or when they're used in very performance-sensitive code where avoiding boxing and inlining of method calls makes a difference.

2 Likes

Yes, sometimes this is reasonable, but it's quite rare, and generics should still be the default. You can always go from a generic function to one that accepts/returns trait objects by erasing the type; you can't do the opposite.

Using a trait object to get rid of type parameters is acceptable when the type parameters would unnecessarily clutter an API. Even so, I'd say this is only true for user-defined types, where you can't avoid spelling out type parameters most of the time. I very rarely find it acceptable for a function to take or return dyn Trait directly.

An example when a trait object would be useful for hiding type parameters may be a system with different kinds of "backends", such as:

  • a compiler or code generator that emits different languages
  • a storage abstraction layer that talks to many kinds of database engines
  • a web API client that can use different concrete HTTP client implementations.

In all of these cases, if you want to "pre-compile" an expression, query, or request, you may not force the user to deal with the dependence of a pre-compiled query upon the particular backend type (including associated error types), because it's largely irrelevant to the correct operation of the system as a whole. So you may want to erase the backend and/or the error type.

A counterexample would be a method that processes an iterator. You should likely not make that method take a &mut dyn Iterator, because by making it generic, you can still call it with any iterator, and the type parameter will be inferred, so there's no inconvenience.

3 Likes

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.