What are generics in Rust and how do trait bounds constrain them?
Learn how Rust generics let one function work over many types, and how trait bounds like T: Ord constrain them — with zero runtime cost via monomorphization.
Expected Interview Answer
Generics let you write functions, structs, and enums that work over many types using type parameters like <T>, and trait bounds (T: Trait) constrain those parameters so the code may only use the capabilities the bound guarantees.
Without a bound, a generic parameter is completely opaque — you can move it but not call methods on it. A trait bound such as T: Display or T: Ord tells the compiler the type provides that behaviour, so you can use it inside the generic body, and callers with a non-conforming type are rejected at compile time. Bounds can be written inline, stacked with +, or moved into a where clause for readability, and they drive monomorphization so the generic code stays as fast as hand-written concrete code.
- Write code once that works for many types with no duplication
- Trait bounds enforce required capabilities at compile time
- No runtime cost — generics monomorphize to concrete code
- Catches type-mismatch errors before the program runs
- where clauses keep complex bounds readable
AI Mentor Explanation
A generic is like a batting slot open to any player, and a trait bound is the selection criterion that only lets in players who can actually bat. Declaring 'any player' alone tells you nothing about their skills, but 'any player who is a certified batter' guarantees they can face the bowling — the constraint is what makes the slot usable.
Step-by-Step Explanation
Step 1
Introduce a type parameter
Declare <T> on a function, struct, or enum so it can operate over many types.
Step 2
Hit the opacity wall
Without a bound, T is opaque — you can move or store it but cannot call trait methods on it.
Step 3
Add a trait bound
Write T: Display or T: Ord to promise the compiler the type provides that behaviour.
Step 4
Combine and clean up
Stack bounds with + and move complex sets into a where clause for readability.
Step 5
Compiler monomorphizes
Each concrete type gets its own specialized copy, so generic code runs as fast as hand-written code.
What Interviewer Expects
- Understanding a generic parameter is opaque until bounded
- Knowing trait bounds unlock specific methods on T
- Familiarity with +, where clauses, and multiple bounds
- Awareness that generics monomorphize with no runtime cost
- Ability to explain a bound like T: Ord enabling comparison
Common Mistakes
- Trying to call methods on an unbounded generic parameter
- Confusing trait bounds with dynamic dispatch / dyn Trait
- Thinking generics add runtime overhead like reflection
- Overusing bounds that aren't actually needed by the body
- Not knowing when to prefer a where clause for readability
Best Answer (HR Friendly)
“Generics let you write one piece of code that works for many different types instead of copying it for each. Trait bounds are conditions you attach — like saying 'this type must be comparable' — so the compiler makes sure only suitable types are used and lets you rely on those abilities inside the code.”
Code Example
use std::fmt::Display;
// T: PartialOrd lets us compare; Copy lets us return by value.
fn largest<T: PartialOrd + Copy>(items: &[T]) -> T {
let mut biggest = items[0];
for &item in items {
if item > biggest {
biggest = item;
}
}
biggest
}
// Same idea with the bound moved into a where clause for readability.
fn announce<T>(value: T)
where
T: Display,
{
println!("the value is {}", value);
}
fn main() {
println!("{}", largest(&[3, 7, 2, 9, 5]));
println!("{}", largest(&[1.5, 0.2, 3.8]));
announce("hello");
}Follow-up Questions
- What can you do with an unbounded generic parameter?
- How do where clauses differ from inline bounds?
- How do generics relate to monomorphization and performance?
- When would you use dyn Trait instead of a generic bound?
- What are associated types and how do they interact with generics?
MCQ Practice
1. What can you do with a generic parameter T that has no trait bound?
An unbounded T is opaque; you can move or store values but cannot call trait methods until a bound guarantees them.
2. What does the bound T: Ord enable inside a generic function?
T: Ord guarantees a total ordering, so comparison operators and methods like max/min become available on T.
3. What runtime cost do Rust generics add compared to concrete code?
Generics are monomorphized at compile time into specialized copies, so they match the speed of hand-written concrete code.
Flash Cards
What is a generic in Rust? — A type parameter like <T> that lets functions, structs, and enums work over many types.
What does a trait bound do? — Constrains a generic parameter (T: Trait) so the code may use the capabilities the trait guarantees.
What can you do with an unbounded T? — Only move or store it — you cannot call trait methods until a bound is added.
Why use a where clause? — To keep complex or numerous trait bounds readable, separated from the signature.
Runtime cost of generics? — None — monomorphization produces specialized concrete code, as fast as hand-written.