Skip to Content
Course content

143: Common Rust Interview Questions on Trait Objects vs Generics

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I once interviewed a candidate who was technically brilliant but stumbled hard on a surprisingly simple question. He had built a sophisticated plugin system for a game engine, and when I asked why he chose Box<dyn Plugin> for his registry instead of a generic T: Plugin, he froze. He knew his code worked, and he'd used the syntax correctly, but he couldn't explain the "why" behind the mechanism. In a high-stakes interview, not knowing the difference between static and dynamic dispatch makes it look like you're guessing with the language rather than mastering it.

The Magic of Monomorphization

When you use generics—like fn execute<T: Task>(task: T)—you're asking Rust to perform static dispatch. I like to think of this as the compiler doing the heavy lifting for you before the program even runs. Through a process called monomorphization, Rust looks at every single place you called execute and generates a dedicated version of that function for every concrete type you used.

If you call it with a ComputeTask and a NetworkTask, the compiler literally writes two different functions in the final binary. This is why generics are blazingly fast; the compiler knows exactly which code to run, meaning it can inline the calls and optimize the hell out of them. The trade-off? Your binary size grows (often called "code bloat") because of all those duplicated versions of the same logic. If you're interviewing for a role working on embedded systems or WASM, this binary size trade-off is exactly what they want to hear you mention.

Navigating the VTable with Trait Objects

Now, generics have a massive limitation: you can't have a Vec<T> that holds different types, even if they all implement the same trait. If you need a list of different things that all "do the same action," you have to move to trait objects, using the dyn keyword (e.g., Box<dyn Task>). This is dynamic dispatch.

Instead of creating a copy of the function for every type, Rust uses a "vtable" (virtual method table). A trait object is actually a fat pointer: it contains one pointer to the data (the instance of your struct) and another pointer to the vtable, which is essentially a map of where the trait's methods are located in memory for that specific type. At runtime, the program follows the pointer to the vtable, finds the right function address, and jumps to it. This indirection is the "cost" of dyn. It's slower than a direct call, and it prevents the compiler from inlining, but it gives you the flexibility to store heterogeneous collections.

Answering the "Which One?" Question

If an interviewer asks you to choose between them, don't just say "generics are faster." That's a textbook answer. Give them the engineering trade-off. Tell them that generics provide the best performance and type safety at the cost of compilation time and binary size. Tell them trait objects provide flexibility and smaller binaries at the cost of a slight runtime overhead and the restriction of "object safety"—the fact that not all traits can be made into trait objects (for instance, if a trait method returns Self, it can't be a trait object because the compiler wouldn't know the size of Self at runtime).

In my experience, the best way to impress is to mention that you'll default to generics for internal logic where performance is key, but switch to dyn at the boundaries of your system—like plugin architectures or event listeners—where you simply cannot know the types at compile time.




📋 Practical Task

Implementing a Heterogeneous Message Dispatcher

Your task is to build a message processing system that can handle different types of alerts (e.g., EmailAlert and SmsAlert) within the same collection. You will implement this using both static and dynamic dispatch to see the difference in practice.

  • Define a trait called Alert with a method send(&self, message: &str).
  • Create two structs, EmailAlert and SmsAlert, that implement this trait.
  • Write a function send_single_alert<T: Alert>(alert: T, msg: &str) that uses static dispatch to send a single alert.
  • Create a Dispatcher struct that holds a Vec<Box<dyn Alert>>.
  • Implement a method for Dispatcher called send_all(&self, msg: &str) that iterates through the collection and triggers send on every alert.
  • In your main function, demonstrate that you can add both an EmailAlert and an SmsAlert to the same Dispatcher and process them in one loop.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.