tezvyn:

Static dispatch over Write with generics in Rust

AI-drafted, machine-checkedSource: interviewintermediate
WHAT IT TESTS

static vs dynamic dispatch trade-offs.

OUTLINE

write generically over the std::io::Write trait with a type parameter W: Write, letting the compiler monomorphize and inline per concrete type, avoiding the vtable indirection of Box<dyn Write>.

WHAT THIS TESTS Whether you understand Rust's two dispatch strategies and can pick static dispatch via generics to abstract over output destinations without the runtime cost of trait objects.

A GOOD ANSWER COVERS All three destinations, standard output, a File, and a TcpStream, already implement the std::io::Write trait, so you abstract over that trait rather than concrete types. Use a generic bound instead of a trait object: write fn emit<W: Write>(w: &mut W, ...) or define struct Writer<W: Write> { inner: W }. With generics the compiler performs monomorphization, generating a specialized copy of the code for each concrete W actually used, which lets it inline write calls and optimize aggressively, with no vtable. By contrast Box<dyn Write> stores a fat pointer and dispatches each call through a vtable, adding indirection that blocks inlining. The trade-offs of the generic approach are larger binary size from duplicated code and that a single generic value is fixed to one concrete type, so you cannot store a heterogeneous collection of differing writers in one Vec without erasing to dyn.

COMMON WRONG ANSWERS Claiming generics and trait objects have identical performance. Reaching for Box<dyn Write> by default when the type is known at compile time. Believing impl Write in argument position is fundamentally different from a generic bound; it is essentially sugar for an anonymous generic. Forgetting that you still need dyn when the concrete type is chosen at runtime.

LIKELY FOLLOW-UPS When is Box<dyn Write> actually the right choice, such as storing mixed writers or reducing compile time and binary size? What does monomorphization cost in build time? How does impl Trait relate to generics? How would you accept the destination via configuration at runtime and reconcile that with static dispatch?

ONE CONCRETE EXAMPLE struct Report<W: Write> { out: W }; impl<W: Write> Report<W> { fn write_line(&mut self, s: &str) -> io::Result<()> { writeln!(self.out, "{s}") } }. Constructing Report { out: io::stdout() }, Report { out: file }, or Report { out: tcp_stream } yields three monomorphized, inlinable versions with zero vtable overhead, whereas Box<dyn Write> would dispatch dynamically at each writeln.

Read the original → doc.rust-lang.org

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.