Traits
Overview
Traits define shared behavior that types can implement. They’re Rust’s primary mechanism for polymorphism—similar to interfaces in other languages, but more powerful with features like default implementations and associated types.
flowchart TD
subgraph Trait["trait Summary"]
M["fn summarize(&self) -> String"]
end
subgraph Implementations
A["Article"] -->|implements| Trait
T["Tweet"] -->|implements| Trait
B["Book"] -->|implements| Trait
end
subgraph Usage["Generic Code"]
F["fn notify(item: &impl Summary)"]
end
Trait --> F
Key insight: Traits let you write code that works with any type implementing certain behavior, enabling both code reuse and abstraction.
When to Use Traits
| Scenario | Use Trait | Alternative |
|---|---|---|
| Define shared behavior | ✓ | - |
| Accept multiple types with same behavior | ✓ | Enum if fixed set |
| Operator overloading (+, -, ==) | ✓ | - |
| Enable external crate types in your code | ✓ | - |
| Simple data grouping | ✗ | Struct |
| Fixed set of variants | ✗ | Enum |
flowchart TD
A{What do you need?} -->|Shared behavior across types| B[Use Trait]
A -->|Fixed set of variants| C[Use Enum]
A -->|Group related data| D[Use Struct]
B --> E{Known at compile time?}
E -->|Yes| F["impl Trait / generics<br/>(static dispatch)"]
E -->|No| G["dyn Trait<br/>(dynamic dispatch)"]
style B fill:#90EE90
style F fill:#87CEEB
style G fill:#FFE4B5
Defining and Implementing Traits
Basic Trait Definition
trait Summary {
fn summarize(&self) -> String;
}
Implementing for Types
struct Article {
title: String,
author: String,
content: String,
}
impl Summary for Article {
fn summarize(&self) -> String {
format!("{}, by {}", self.title, self.author)
}
}
struct Tweet {
username: String,
content: String,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("@{}: {}", self.username, self.content)
}
}
sequenceDiagram
participant Code
participant Article
participant Tweet
Code->>Article: article.summarize()
Article-->>Code: "Rust Guide, by Jane"
Code->>Tweet: tweet.summarize()
Tweet-->>Code: "@rustlang: Hello!"
Note over Code: Same method, different implementations
Using Traits
fn main() {
let article = Article {
title: String::from("Rust is Great"),
author: String::from("Jane Doe"),
content: String::from("..."),
};
let tweet = Tweet {
username: String::from("rustlang"),
content: String::from("Hello, Rustaceans!"),
};
println!("{}", article.summarize());
println!("{}", tweet.summarize());
}
Default Implementations
Traits can provide default behavior that implementors can override:
trait Summary {
// Required: implementors must provide this
fn summarize_author(&self) -> String;
// Default: uses summarize_author, can be overridden
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
// Uses default summarize()
}
impl Summary for Article {
fn summarize_author(&self) -> String {
self.author.clone()
}
// Override default
fn summarize(&self) -> String {
format!("{} by {}", self.title, self.author)
}
}
flowchart TD
subgraph Trait["trait Summary"]
R["summarize_author() - Required"]
D["summarize() - Default impl"]
end
subgraph Tweet
T1["summarize_author() ✓"]
T2["summarize() → uses default"]
end
subgraph Article
A1["summarize_author() ✓"]
A2["summarize() → overrides"]
end
R --> T1
R --> A1
D -.->|inherited| T2
D -.->|overridden| A2
Traits as Parameters
impl Trait Syntax (Simple)
// Accept any type implementing Summary
fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
// Works with any Summary implementor
notify(&article);
notify(&tweet);
Trait Bound Syntax (Flexible)
// Equivalent to impl Trait, but more explicit
fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
// Useful when you need the same type for multiple params
fn notify_twice<T: Summary>(a: &T, b: &T) {
// a and b must be the same concrete type
}
Multiple Trait Bounds
use std::fmt::Display;
// Must implement both traits
fn notify(item: &(impl Summary + Display)) {
println!("{}", item); // Display
println!("{}", item.summarize()); // Summary
}
// Trait bound syntax
fn notify<T: Summary + Display>(item: &T) {
// ...
}
where Clauses for Complex Bounds
// Without where - hard to read
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
// ...
}
// With where - cleaner
fn some_function<T, U>(t: &T, u: &U) -> i32
where
T: Display + Clone,
U: Clone + Debug,
{
// ...
}
flowchart TD
A{How to specify bounds?} -->|Simple, one trait| B["impl Trait"]
A -->|Multiple traits| C["T: Trait1 + Trait2"]
A -->|Complex bounds| D["where clause"]
A -->|Same type multiple times| E["Generic T: Trait"]
B --> F["fn foo(x: &impl Summary)"]
C --> G["fn foo(x: &(impl A + B))"]
D --> H["fn foo<T>() where T: A + B"]
E --> I["fn foo<T: Summary>(a: &T, b: &T)"]
Returning Types that Implement Traits
impl Trait in Return Position
fn create_summarizable() -> impl Summary {
Tweet {
username: String::from("horse_ebooks"),
content: String::from("of course"),
}
}
You can only return one concrete type with impl Trait:
// ❌ WON'T COMPILE - different concrete types
fn create_summarizable(switch: bool) -> impl Summary {
if switch {
Article { /* ... */ } // One type
} else {
Tweet { /* ... */ } // Different type!
}
}
Trait Objects for Dynamic Dispatch
Use dyn Trait when you need runtime polymorphism:
// Can hold different types at runtime
fn create_summarizable(switch: bool) -> Box<dyn Summary> {
if switch {
Box::new(Article { /* ... */ })
} else {
Box::new(Tweet { /* ... */ })
}
}
// Accept mixed collection
fn print_all(items: &[&dyn Summary]) {
for item in items {
println!("{}", item.summarize());
}
}
flowchart TD
subgraph "Static Dispatch (impl Trait)"
S1["Compile time: one concrete type"]
S2["Zero overhead"]
S3["Monomorphized"]
end
subgraph "Dynamic Dispatch (dyn Trait)"
D1["Runtime: multiple types possible"]
D2["Vtable lookup overhead"]
D3["Single compiled function"]
end
A{Need multiple<br/>concrete types?} -->|No| S1
A -->|Yes| D1
Common Standard Library Traits
Display and Debug
use std::fmt;
struct Point {
x: i32,
y: i32,
}
// Display: user-friendly output with {}
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
// Debug: programmer-friendly output with {:?}
impl fmt::Debug for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Point")
.field("x", &self.x)
.field("y", &self.y)
.finish()
}
}
// Or derive Debug automatically
#[derive(Debug)]
struct Point2 { x: i32, y: i32 }
Clone and Copy
// Clone: explicit duplication
#[derive(Clone)]
struct DeepClone {
data: Vec<i32>, // Vec must be cloned explicitly
}
// Copy: implicit bitwise copy (must also be Clone)
#[derive(Clone, Copy)]
struct ShallowCopy {
x: i32,
y: i32,
}
fn main() {
let a = ShallowCopy { x: 1, y: 2 };
let b = a; // Copy - a still valid
println!("{}, {}", a.x, b.x);
let c = DeepClone { data: vec![1, 2, 3] };
let d = c.clone(); // Must explicitly clone
// c is still valid
}
flowchart TD
A{Can type be Copy?} -->|All fields are Copy| B[Derive Copy + Clone]
A -->|Has heap data| C[Derive Clone only]
A -->|Complex ownership| D[Implement Clone manually]
B --> E["Implicit copy on assignment"]
C --> F["Explicit .clone() required"]
PartialEq and Eq
#[derive(PartialEq)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = Point { x: 1, y: 2 };
let p3 = Point { x: 3, y: 4 };
assert!(p1 == p2); // PartialEq enables ==
assert!(p1 != p3); // and !=
}
// Eq: marker trait for types where a == a is always true
// (floats can't be Eq because NaN != NaN)
#[derive(PartialEq, Eq)]
struct Integer(i32);
From and Into
struct Celsius(f64);
struct Fahrenheit(f64);
impl From<Celsius> for Fahrenheit {
fn from(c: Celsius) -> Self {
Fahrenheit(c.0 * 9.0 / 5.0 + 32.0)
}
}
fn main() {
let c = Celsius(100.0);
// From
let f1 = Fahrenheit::from(c);
// Into (auto-implemented when From exists)
let c2 = Celsius(0.0);
let f2: Fahrenheit = c2.into();
// Works in function calls too
fn process(f: Fahrenheit) { /* ... */ }
process(Celsius(50.0).into());
}
flowchart LR
A[Celsius] -->|From| B[Fahrenheit]
A -->|Into| B
Note["Implement From, get Into free"]
Default
#[derive(Default)]
struct Config {
debug: bool, // defaults to false
timeout: u32, // defaults to 0
name: String, // defaults to ""
}
fn main() {
// All defaults
let config1 = Config::default();
// Partial override
let config2 = Config {
debug: true,
..Default::default()
};
}
Operator Overloading
Implement traits from std::ops to enable operators:
use std::ops::Add;
#[derive(Debug, Copy, Clone)]
struct Point {
x: i32,
y: i32,
}
impl Add for Point {
type Output = Point; // Associated type
fn add(self, other: Point) -> Point {
Point {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
fn main() {
let p1 = Point { x: 1, y: 0 };
let p2 = Point { x: 2, y: 3 };
let p3 = p1 + p2; // Uses Add trait
println!("{:?}", p3); // Point { x: 3, y: 3 }
}
| Operator | Trait | Method |
|---|---|---|
+ |
Add |
add(self, rhs) |
- |
Sub |
sub(self, rhs) |
* |
Mul |
mul(self, rhs) |
/ |
Div |
div(self, rhs) |
== |
PartialEq |
eq(&self, other) |
< > |
PartialOrd |
partial_cmp(&self, other) |
[] |
Index |
index(&self, idx) |
*x |
Deref |
deref(&self) |
Supertraits
Require another trait to be implemented:
use std::fmt;
// OutlinePrint requires Display
trait OutlinePrint: fmt::Display {
fn outline_print(&self) {
let output = self.to_string(); // Uses Display
let len = output.len();
println!("{}", "*".repeat(len + 4));
println!("* {} *", output);
println!("{}", "*".repeat(len + 4));
}
}
struct Point { x: i32, y: i32 }
// Must implement Display first
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
// Now can implement OutlinePrint
impl OutlinePrint for Point {}
flowchart TD
A["trait OutlinePrint: Display"] -->|requires| B[Display]
C[Point] -->|must implement| B
C -->|then can implement| A
Associated Types
Define placeholder types within a trait:
trait Iterator {
type Item; // Associated type
fn next(&mut self) -> Option<Self::Item>;
}
struct Counter {
count: u32,
}
impl Iterator for Counter {
type Item = u32; // Specify concrete type
fn next(&mut self) -> Option<Self::Item> {
self.count += 1;
if self.count < 6 {
Some(self.count)
} else {
None
}
}
}
Why Associated Types vs Generics?
// With generics: can implement multiple times
trait Container<T> {
fn get(&self) -> T;
}
impl Container<i32> for MyType { /* ... */ }
impl Container<String> for MyType { /* ... */ }
// With associated types: one implementation per type
trait Container {
type Item;
fn get(&self) -> Self::Item;
}
impl Container for MyType {
type Item = i32; // Only one choice
fn get(&self) -> i32 { /* ... */ }
}
Blanket Implementations
Implement a trait for all types matching a bound:
// From std: ToString for all Display types
impl<T: Display> ToString for T {
fn to_string(&self) -> String {
// Uses Display::fmt internally
}
}
// Now any Display type has to_string()
struct Point { x: i32, y: i32 }
impl Display for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
// Point automatically gets to_string()!
let s = Point { x: 1, y: 2 }.to_string();
Common Derive Traits
| Trait | Purpose | Derive? | Notes |
|---|---|---|---|
Debug |
{:?} formatting |
Yes | Almost always derive |
Display |
{} formatting |
No | Must implement manually |
Clone |
.clone() |
Yes | For explicit copies |
Copy |
Implicit copy | Yes | Only for simple stack types |
PartialEq |
== comparison |
Yes | Usually derive |
Eq |
Strict equality | Yes | Marker, requires PartialEq |
PartialOrd |
<, > comparison |
Yes | Usually derive |
Ord |
Total ordering | Yes | Requires Eq |
Hash |
Hashing | Yes | For HashMap keys |
Default |
Default value | Yes | When sensible defaults exist |
// Common derive combination
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
struct User {
id: u64,
name: String,
}
Summary
mindmap
root((Traits))
Defining
trait keyword
Required methods
Default implementations
Using
impl Trait
Generic bounds
where clauses
Dispatch
Static (monomorphization)
Dynamic (dyn Trait)
Std Traits
Debug/Display
Clone/Copy
Eq/Ord
From/Into
Default
Advanced
Associated types
Supertraits
Blanket impls
| Concept | Syntax | Use Case |
|---|---|---|
| Define trait | trait Name { } |
Declare shared behavior |
| Implement | impl Trait for Type |
Provide behavior |
| Bound parameter | fn f(x: &impl Trait) |
Accept any implementor |
| Generic bound | fn f<T: Trait>(x: &T) |
More flexible bounds |
| Multiple bounds | T: Trait1 + Trait2 |
Require multiple traits |
| Where clause | where T: Trait |
Clean complex bounds |
| Trait object | &dyn Trait |
Runtime polymorphism |
Exercises
- Create a
Drawabletrait with adraw()method and implement it forCircleandRectangle - Implement
Displayfor a customTemperaturetype - Create a
Validatortrait and implement it for different validation rules
See Also
- Advanced Traits - Associated types, supertraits, and more
- Generics - Type parameters and trait bounds
- Example Code
Next Steps
Learn about Generics to write flexible, reusable code.