Courses / Rust Programming: Safe and Fast
Lesson 11 of 19
Traits and Generics in Rust
Master Rust traits and generics — defining traits, trait bounds, default methods, trait objects, and generic data structures.
What you'll learn
- ✓How to define and implement traits
- ✓Trait bounds and where clauses for generics
- ✓Static dispatch vs dynamic dispatch (trait objects)
- ✓Common standard library traits
Prerequisites
- •Rust basics (structs, enums, functions)
- •Understanding of ownership and references
Traits are Rust’s mechanism for shared behavior — similar to interfaces in Go or Java, but with default implementations, associated types, and zero-cost static dispatch.
Defining a trait
trait Summary {
fn summarize(&self) -> String;
}
Implementing a trait
struct Article {
title: String,
content: String,
}
impl Summary for Article {
fn summarize(&self) -> String {
format!("{}: {}...", self.title, &self.content[..50])
}
}
struct Tweet {
username: String,
text: String,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("@{}: {}", self.username, self.text)
}
}
Default methods
trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
// summarize() uses the default implementation
}
Traits as parameters
impl Trait syntax
fn notify(item: &impl Summary) {
println!("Breaking: {}", item.summarize());
}
Trait bound syntax
fn notify<T: Summary>(item: &T) {
println!("Breaking: {}", item.summarize());
}
Multiple bounds
fn notify(item: &(impl Summary + Display)) {
println!("{}", item);
}
// or with trait bound syntax
fn notify<T: Summary + Display>(item: &T) {
println!("{}", item);
}
Where clauses
For complex bounds, use where for readability.
fn process<T, U>(t: &T, u: &U) -> String
where
T: Summary + Clone,
U: Display + Debug,
{
format!("{} — {:?}", t.summarize(), u)
}
Returning impl Trait
fn create_summarizable() -> impl Summary {
Tweet {
username: "bot".to_string(),
text: "Hello world".to_string(),
}
}
The caller does not know the concrete type — only that it implements Summary.
Generics
Generic functions
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut max = &list[0];
for item in &list[1..] {
if item > max {
max = item;
}
}
max
}
let numbers = vec![34, 50, 25, 100, 65];
println!("Largest: {}", largest(&numbers));
let chars = vec!['y', 'm', 'a', 'q'];
println!("Largest: {}", largest(&chars));
Generic structs
struct Point<T> {
x: T,
y: T,
}
impl<T: Display> Point<T> {
fn print(&self) {
println!("({}, {})", self.x, self.y);
}
}
impl Point<f64> {
fn distance(&self) -> f64 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
Multiple generic types
struct Pair<A, B> {
first: A,
second: B,
}
Trait objects (dynamic dispatch)
When you need to store different types that implement the same trait, use dyn Trait.
fn get_items() -> Vec<Box<dyn Summary>> {
vec![
Box::new(Article {
title: "AI News".to_string(),
content: "Artificial intelligence is transforming...".to_string(),
}),
Box::new(Tweet {
username: "rustlang".to_string(),
text: "Rust 2024 edition is here!".to_string(),
}),
]
}
Static vs dynamic dispatch
| Feature | impl Trait / generics | dyn Trait |
|---|---|---|
| Dispatch | Static (monomorphized) | Dynamic (vtable) |
| Performance | Zero-cost | Pointer indirection |
| Concrete type known at | Compile time | Runtime |
| Can store mixed types | No | Yes |
Use generics (static dispatch) by default. Use dyn when you need a collection of mixed types or the concrete type is decided at runtime.
Common standard library traits
| Trait | Purpose |
|---|---|
Display | Format for users ({}) |
Debug | Format for developers ({:?}) |
Clone | Deep copy with .clone() |
Copy | Implicit bit-copy on assignment |
PartialEq, Eq | Equality comparison |
PartialOrd, Ord | Ordering comparison |
Hash | Hash for use in HashMap/HashSet |
Default | Provide a default value |
From/Into | Type conversions |
Iterator | Iteration protocol |
Deriving traits
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct User {
name: String,
age: u32,
}
Associated types
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
struct Counter {
count: u32,
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
self.count += 1;
if self.count <= 5 {
Some(self.count)
} else {
None
}
}
}
Summary
Traits define shared behavior. Generics with trait bounds give you static dispatch and zero-cost abstractions. Trait objects (dyn Trait) give you dynamic dispatch when you need runtime polymorphism. Start with generics, reach for trait objects when the concrete type cannot be known at compile time, and derive standard traits to get common functionality for free.
Progress is saved locally to your browser.