← Home ← Codex ← DEBT ← Engine
Browse by Category
+ added · updated 7d
← Back to glossary

Rust Generics

Rust Intermediate
debt(d2/e3/b4/t5)
d2 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'caught instantly' (d1) but slightly worse at d2; missing trait bounds are compiler errors caught instantly, and clippy flags over-generification, though performance traps like monomorphization bloat stay silent until measured.

e3 Effort Remediation debt — work required to fix once spotted

Closest to 'simple parameterised fix' (e3); the quick_fix is adding a trait bound (T: Display) or swapping Box<dyn Trait> for a generic bound, a small localized change but sometimes ripples through a function's signature and call sites.

b4 Burden Structural debt — long-term weight of choosing wrong

Closest to 'localised tax' (b3) leaning to b4; generics applied across library/web/cli contexts shape public APIs, and heavy use creates a persistent productivity and compile-time/binary-size tax that touches multiple work streams.

t5 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'notable trap' (t5); the misconception that Rust generics carry runtime overhead like reflection or boxing is a documented gotcha — in reality monomorphization is zero-cost — and devs commonly confuse generics with trait objects.

About DEBT scoring →

Also Known As

rust type parameters generic functions rust rust monomorphization

TL;DR

Generics let you write code parameterized over types, with compile-time type safety and zero runtime cost via monomorphization.

Explanation

Generics in Rust let you write functions, structs, enums, and methods that operate over any type rather than a single concrete one. You introduce type parameters with angle brackets, as in `fn largest<T>(list: &[T]) -> &T` or `struct Wrapper<T> { value: T }`. The same code then works for many types while the compiler still checks every usage statically. There is no boxing, no runtime type tags, and no reflection involved by default.

The defining mechanism is monomorphization. When you call a generic function with `i32` and `String`, the compiler generates a specialized copy for each concrete type at compile time, exactly as if you had written both versions by hand. This means generic code runs as fast as hand-specialized code - there is zero runtime dispatch overhead. The trade-off is larger binaries and longer compile times when many instantiations exist, which is the opposite trade-off from dynamic dispatch through `dyn`.

Generics rarely stand alone. To do anything useful with a value of type `T` you must constrain it with trait bounds: `fn print_all<T: Display>(items: &[T])` says T must implement Display. Without a bound the compiler only knows the type exists, not what operations it supports, so attempts to add, compare, or print it fail. Bounds can be written inline or in a `where` clause for readability, and you can require multiple traits with `T: Clone + Ord`.

Generics also power associated types, generic methods on impl blocks, and generic enums like `Option<T>` and `Result<T, E>` that underpin the entire standard library. A common confusion is mixing generics (static, monomorphized, one type chosen per call site) with trait objects (`Box<dyn Trait>`, dynamic, one type erased behind a vtable). Generics give you compile-time specialization and zero cost; trait objects give you runtime heterogeneity at the price of indirection.

Mastering generics means understanding type parameters, trait bounds as the gateway to actually using generic values, monomorphization and its code-size implications, and when a generic bound beats a trait object.

Common Misconception

Generics in Rust carry runtime overhead like reflection or boxing in other languages. In reality the compiler monomorphizes each instantiation into specialized code with zero runtime cost.

Why It Matters

Misunderstanding bounds leads to code that will not compile, while confusing generics with trait objects produces either needless heap allocation or surprising code bloat in performance-sensitive services and libraries.

Common Mistakes

  • Writing a generic function that uses an operation like comparison or printing without adding the required trait bound such as Ord or Display.
  • Reaching for Box<dyn Trait> for runtime dispatch when a generic with a trait bound would be zero-cost and simpler.
  • Over-generifying a function that only ever receives one concrete type, adding noise without any reuse benefit.
  • Forgetting that heavy use of generics causes monomorphization bloat, inflating binary size and compile times.
  • Assuming you can name a single generic instantiation at runtime, when each call site fixes the type at compile time.

Avoid When

  • A function or struct will only ever work with a single concrete type, where a generic parameter adds complexity for no reuse.
  • You need a heterogeneous collection of differing concrete types behind one interface, which calls for trait objects instead.
  • Excessive instantiations would bloat the binary and compile times beyond what the performance gain justifies.

When To Use

  • Writing functions or data structures that should operate over many types while preserving compile-time type safety.
  • Achieving zero-cost abstraction in performance-critical code where dynamic dispatch overhead is unacceptable.
  • Building reusable library APIs like containers, algorithms, and wrappers parameterized over caller-chosen types.
  • Combining with trait bounds to express exactly what capabilities a generic type must provide.

Code Examples

✗ Vulnerable
// Generic without a bound: the compiler does not know T can be compared,
// so this fails to compile.
fn largest<T>(list: &[T]) -> &T {
    let mut biggest = &list[0];
    for item in list {
        if item > biggest { // error: binary operation `>` cannot be applied to type `&T`
            biggest = item;
        }
    }
    biggest
}

fn main() {
    let numbers = vec![34, 50, 25, 100, 65];
    println!("{}", largest(&numbers));
}
✓ Fixed
// Add a trait bound so T supports comparison; monomorphized per concrete type,
// zero runtime cost.
fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut biggest = &list[0];
    for item in list {
        if item > biggest {
            biggest = item;
        }
    }
    biggest
}

fn main() {
    let numbers = vec![34, 50, 25, 100, 65];
    let chars = vec!['y', 'm', 'a', 'q'];
    println!("{}", largest(&numbers));
    println!("{}", largest(&chars));
}

Added 18 Jun 2026
Views 58
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings S 0 pings M 2 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 1 ping W 0 pings T 0 pings F 1 ping S 0 pings S 1 ping M 1 ping T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 1 ping T 0 pings F 0 pings S 1 ping S 0 pings M
No pings yet today
Amazonbot 1
PetalBot 8 Google 3 Perplexity 3 Ahrefs 3 ChatGPT 2 Twitter/X 2 Brave Search 2 Applebot 2 Amazonbot 2 Bing 2
crawler 27 crawler_json 2
DEV INTEL Tools & Severity
🟢 Low ⚙ Fix effort: Low
⚡ Quick Fix
Add the trait bound that supplies the operation you need (e.g. T: PartialOrd or T: Display), and prefer generic bounds over Box<dyn Trait> unless you need runtime heterogeneity.
📦 Applies To
web cli queue-worker library
🔗 Prerequisites
🔍 Detection Hints
fn\s+\w+<[^>]*[A-Z]\w*[^>]*>\s*\([^)]*\)
Auto-detectable: ✓ Yes clippy
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: Medium ✗ Manual fix Fix: Low Context: Function


✓ schema.org compliant