Monomorphisation is why your binary is big
Rust generics are monomorphised: the compiler emits a separate copy of the function for every
concrete type it is instantiated with. Vec<u8> and Vec<String>
do not share code. Neither does a generic function called with four different types — that is four
functions in the binary.
This is what makes generic code as fast as hand-written code. Everything inlines, everything devirtualises, there is no vtable indirection. It is the zero-cost abstraction working exactly as advertised, at runtime.
The bill arrives elsewhere: compile time and binary size. A deeply generic library instantiated across many types can multiply its own code by a large constant, and the effect compounds through call chains, because a generic function calling another generic function multiplies both.
Where it actually hurts
Less often than people fear. In most binaries, the dominant contributors are the standard
library's formatting machinery, panic infrastructure with its unwinding tables, and debug info —
not your generics. Measure before restructuring anything: cargo bloat attributes size
to crates and functions, and the answer is frequently somewhere you were not looking.
The lever, when you need it
Swap static dispatch for dynamic at the boundary where the type stops mattering — take
&dyn Trait instead of impl Trait and you get one copy instead of N.
The common refinement is an outer generic function that does the ergonomic conversion and
immediately calls a small non-generic inner one, so only the thin wrapper is duplicated while the
body exists once.
Also worth knowing: strip = "symbols" and panic = "abort" in the release
profile each remove a meaningful fraction, and neither costs you anything at runtime — the second
only matters if you were catching unwinds, which almost nobody is.