Best practices for generics vs trait objects (for references)

I am interested in hearing how people decide on when to use generics vs trait objects when both are possible. For the sake of this question, I am focusing on references so &T vs &dyn Trait but if you have advice for other cases like (T vs Box<dyn T>) then I'm also happy to hear it.

As an example, I have a special-purpose Logger trait that is used by a handful of parts of my code. When defining traits that use it, they could either be generic or take a trait object:

pub trait Logger {}

pub trait TraitGeneric<L: ?Sized> {
    fn foo(logger: &mut L);

pub trait TraitDyn {
   fn foo(logger: &mut dyn Logger);

(I prefer for TraitGeneric to be object safe so no foo<L>).

My impression is that generics are usually preferred and, as far as I am aware, TraitGeneric is a superset of TraitDyn because you can always use TraitGeneric<dyn Logger>. On the other hand, using generics means adding a lot of L: Logger + ?Sized bounds on any code that uses loggers, just to avoid some potential vtable lookups (assuming I don't end up specifying TraitGeneric<dyn Logger> anyway to avoid the compilation cost of multiple monomorphizations).

So what would you do in this situation? How do you think about the trade-offs between generics and trait objects?

1 Like

The traits that I think it makes sense to compare are:

pub trait TraitGeneric {
    fn foo<L: ?Sized>(logger: &mut L);

pub trait TraitDyn {
   fn foo(logger: &mut dyn Logger);

If you put the generic on the trait rather than on the method, then that does something different.

As for the above two, it depends on whether I want it to be object safe.

1 Like

I think it's far more common for people to overuse generics than to overuse dyn.

So long as the thing you're calling is doing non-trivial work, I'd say it's better to start by using &dyn Trait. That gives a bunch of separate compilation goodness, and you can change it to impl Trait later if it turns out to be perf-critical.


That definitely describes me -- a significant fraction of my code has become trait bounds and I was wondering whether its possible to be overusing them or if that's just what idiomatic rust looks like.

Thanks for the advice!

Yeah, it does seem conceptually better to have the generic be on the method than on the trait in this case. I do want it to be object safe so looks like I'll be going for &dyn Trait, thanks!

For short reusable methods, I definitely use generics. The standard library is full of those.

But otherwise, it's best to measure. Remember also that generics can have weird effects, such as code staying out of cache significantly slowing down your program, seemingly with no relationship to the generics, even a lot of time after the code was written. Or compile times.

Specifically about logging, I'd use trait objects, as loggers are something that you inject/replace usually, and not performance critical, I don't think it's a good idea to duplicate your code for each kind of logger. It can be wrong if you only plan to use one kind of logger, and more than that if you plan to turn it off for some builds, then generics will allow the compiler to eliminate the call entirely.


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.