monomorphization
/ MON-oh-MOR-fih-zay-shun /
When you write one generic function, like fn max<T: Ord>(a: T, b: T) -> T, you have written code that works for many types — i32, String, your own struct. But the machine ultimately runs concrete instructions on concrete data; there is no single piece of machine code that can compare both two integers and two strings, because the comparison itself differs. So how does one generic source become real code? Monomorphization is the answer Rust (and C++ templates) gives: at compile time, for every concrete type the generic is actually used with, the compiler generates a separate, fully specialised copy of the code with that type plugged in.
Picture it as the compiler making stamped copies. If your program calls max(3, 4) and max("a", "b"), the compiler produces two functions behind the scenes — one max_for_i32 and one max_for_String — each with the comparison resolved to that type's exact implementation and no trace of generics left. The name says it: 'mono-morph' means 'one form' — each output function has a single concrete type, turned from one polymorphic ('many forms') source into several monomorphic ones. Because each copy is concrete, the calls inside it are direct (static dispatch) and the optimiser can inline and specialise aggressively, so a generic algorithm runs exactly as fast as a hand-written one for each type. This is what people mean when they call Rust generics a 'zero-cost abstraction': the abstraction exists in the source but is gone, fully specialised, in the binary.
Why this matters: monomorphization is the engine behind static dispatch's speed, but speed is not free — it has a real and honest cost in code size and compile time. Each distinct type you instantiate a generic with produces another copy of the machine code, so a heavily generic library used with many types can bloat the binary and slow down compilation noticeably (this is a well-known reason Rust and C++ compile slower than you might expect). The genuine tradeoff against dynamic dispatch is exactly this: monomorphization gives fast, inlinable, duplicated code; dyn Trait gives one shared copy with an indirection. When binary size or compile time hurts, that is the signal to consider trading some monomorphised generics for trait objects.
fn largest<T: PartialOrd + Copy>(a: T, b: T) -> T { if a > b { a } else { b } } largest(3i32, 9i32); // compiler stamps out largest::<i32> largest(2.5f64, 1.0f64); // and a SEPARATE largest::<f64> // Two concrete copies in the binary; each call is direct and inlinable. // More types used -> more copies -> larger binary, slower compile.
One generic source becomes one concrete copy per type used — fast and inlinable, but the copies add up in code size.
Monomorphization is why generics are 'zero-cost' at run time but not free at build time: each type instantiation duplicates the machine code, growing the binary and slowing compilation. That code-size cost is the real counterweight to dynamic dispatch's single shared copy.