Introduction
Generics allow you to write code that operates on abstract types rather than one concrete type, avoiding duplication. A function or struct can be parameterized with a type placeholder, commonly written T, and the compiler generates specialized versions for each concrete type actually used — a process called monomorphization. This means generics have no runtime overhead compared to hand-written type-specific code.
Cricket analogy: Generics let you write one largest_score<T> function that works for both ODI and Test match scores instead of duplicating it; the compiler monomorphizes a specialized version for each concrete score type used, at no runtime cost.
Syntax
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
struct Point<T> {
x: T,
y: T,
}Explanation
The <T: PartialOrd> syntax declares a generic type parameter T with a trait bound, meaning T must implement PartialOrd so that values can be compared with >. Trait bounds constrain which types are valid, letting the compiler check correctness at compile time. Generic structs like Point<T> store fields of a type chosen when the struct is instantiated, e.g. Point<i32> or Point<f64>. Multiple bounds can be combined, such as <T: Display + Clone>, requiring T to satisfy both traits.
Cricket analogy: The bound <T: PartialOrd> is like requiring any stat type to be comparable so you can rank Kohli's average against Root's with >; a generic ScoreCard<T> then holds a runs field typed as i32 or f64 when instantiated.
Example
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let numbers = vec![34, 50, 25, 100, 65];
let result = largest(&numbers);
println!("The largest number is {}", result);
let chars = vec!['y', 'm', 'a', 'q'];
let result = largest(&chars);
println!("The largest char is {}", result);
}Output
The largest number is 100
The largest char is yKey Takeaways
- Generics parameterize functions, structs, and enums over abstract types.
- Trait bounds like
<T: PartialOrd>restrict which types satisfy the generic constraint. - Monomorphization generates specialized code per concrete type, making generics zero-cost.
- Multiple bounds can be combined with
+, e.g.<T: Display + Clone>. - Generic code is checked for correctness entirely at compile time.
Practice what you learned
1. What is monomorphization?
2. In `fn largest<T: PartialOrd>(list: &[T]) -> &T`, what does `T: PartialOrd` mean?
3. How do you declare a generic struct with a single type parameter?
4. How do you combine multiple trait bounds on a single generic parameter?
5. What is the runtime performance cost of using generics in Rust compared to non-generic equivalent code?
Was this page helpful?
You May Also Like
Traits in Rust
Traits define shared behavior that types can implement, similar to interfaces in other languages.
Trait Objects and Dynamic Dispatch in Rust
Trait objects like `dyn Trait` enable runtime polymorphism for heterogeneous collections, at the cost of dynamic dispatch.
Structs in Rust
Structs let you group related values into a single named type with fields you access by name.
Functions in Rust
Learn how to declare, call, and return values from functions using Rust's fn syntax and expression-based return rules.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics