Can you write this from memory?
Define a trait named `Greet` with a method `greet` that takes `&self` and returns nothing.
Rust traits define shared behavior—think of them as Rust's version of interfaces. Generics let you write code that works with many types. Together, they enable zero-cost abstractions.
The syntax can be tricky: trait bounds, where clauses, impl Trait, associated types, the turbofish. You understand what they do, but when you're writing code you pause: Is it T: Clone or where T: Clone? What's the turbofish syntax again?
This page focuses on trait and generic syntax until the patterns become automatic. You'll practice defining traits, implementing them, and writing generic functions with proper bounds.
The most common generics error. You call a method on a generic T but the compiler has no proof T has that method:
// WRONG: No bound — compiler doesn't know T has Display
// fn print_it<T>(item: T) {
// println!("{}", item); // error[E0277]: T doesn't implement Display
// }
// RIGHT: Add the bound so the compiler knows
fn print_it<T: std::fmt::Display>(item: T) {
println!("{}", item);
}
Without the bound, T could be anything — a struct with no Display impl, a function pointer, anything. The bound is your promise to the compiler: "I'll only call this with types that implement Display."
Multiple bounds use +:
fn log_it<T: Display + Debug>(item: T) {
println!("display: {item}, debug: {item:?}");
}
You cannot implement a foreign trait on a foreign type. Both the trait or the type must be defined in your crate:
// WRONG: Display (std) + Vec<i32> (std) — neither is yours
// impl std::fmt::Display for Vec<i32> {
// fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
// write!(f, "{:?}", self) // error[E0117]: orphan rule
// }
// }
// RIGHT: Wrap in a newtype (the newtype IS yours)
struct Scores(Vec<i32>);
impl std::fmt::Display for Scores {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self.0)
}
}
The orphan rule prevents two crates from providing conflicting implementations of the same trait for the same type. The newtype pattern is the standard workaround. For a compact reference of trait syntax and bounds, see the traits cheat sheet.
Your impl Trait for Type block is missing required methods:
trait Summary {
fn summarize(&self) -> String;
fn author(&self) -> String;
}
// WRONG: missing author()
// impl Summary for Article {
// fn summarize(&self) -> String {
// self.title.clone() // error[E0046]: not all trait items implemented
// }
// }
// RIGHT: implement every required method
struct Article { title: String, writer: String }
impl Summary for Article {
fn summarize(&self) -> String { self.title.clone() }
fn author(&self) -> String { self.writer.clone() }
}
Only methods with default implementations can be omitted. If the trait defines a method without a body, you must implement it.
Inline bounds work for simple cases. Use where when bounds get long or when you have multiple type parameters:
// Inline: clean for one or two bounds
fn notify<T: Summary>(item: &T) {
println!("{}", item.summarize());
}
// Where clause: readable with complex bounds
fn process<T, U>(t: &T, u: &U) -> String
where
T: Display + Clone,
U: Clone + Debug,
{
format!("{}", t)
}
Both forms are equivalent. where just moves the bounds after the signature for readability.
impl Trait has two uses with different semantics:
// Argument position: shorthand for a generic
// Equivalent to fn notify<T: Summary>(item: &T)
fn notify(item: &impl Summary) {
println!("{}", item.summarize());
}
// Return position: hides the concrete type
fn make_iter() -> impl Iterator<Item = i32> {
vec![1, 2, 3].into_iter()
}
Return-position gotcha: the function must return exactly one concrete type. You cannot conditionally return different types:
// WRONG: two different concrete types
// fn make_iter(ascending: bool) -> impl Iterator<Item = i32> {
// if ascending {
// vec![1, 2, 3].into_iter() // IntoIter<i32>
// } else {
// vec![3, 2, 1].into_iter().rev() // Rev<IntoIter<i32>>
// }
// }
// RIGHT: use Box<dyn Iterator> for multiple return types
fn make_iter(ascending: bool) -> Box<dyn Iterator<Item = i32>> {
if ascending {
Box::new(vec![1, 2, 3].into_iter())
} else {
Box::new(vec![3, 2, 1].into_iter().rev())
}
}
dyn Trait uses a vtable for runtime dispatch, unlike generics which are monomorphized at compile time:
// Static dispatch (generics): monomorphized, zero cost, larger binary
fn print_static(item: &impl Display) {
println!("{item}");
}
// Dynamic dispatch (trait objects): vtable lookup, flexible, slight overhead
fn print_dynamic(item: &dyn Display) {
println!("{item}");
}
// Trait objects in collections — the classic use case
let shapes: Vec<Box<dyn Shape>> = vec![
Box::new(Circle { radius: 1.0 }),
Box::new(Square { side: 2.0 }),
];
Object safety: a trait can only be used as dyn Trait if all its methods have a receiver (&self, &mut self, etc.) and don't return Self. Traits with fn clone(&self) -> Self are not object-safe. Trait objects enable the Strategy design pattern in Rust, where different implementations are selected at runtime through dyn Trait.
When Rust cannot infer the type, use the turbofish ::<> to specify it:
let n = "42".parse::<i32>().unwrap();
let collected = vec![1, 2, 3].into_iter().collect::<Vec<_>>();
Fully Qualified Syntax (UFCS)
When a type implements multiple traits with the same method name, use fully qualified syntax to disambiguate:
trait Pilot { fn fly(&self); }
trait Wizard { fn fly(&self); }
struct Human;
impl Pilot for Human {
fn fly(&self) { println!("captain speaking"); }
}
impl Wizard for Human {
fn fly(&self) { println!("up!"); }
}
let h = Human;
Pilot::fly(&h); // "captain speaking"
Wizard::fly(&h); // "up!"
// For associated functions (no self), use full path:
// <Type as Trait>::function()
When a trait has exactly one "natural" type parameter, use an associated type. This means each type can only implement the trait once:
// Associated type: one implementation per type
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
// Generic parameter: multiple implementations per type
trait Add<Rhs = Self> {
type Output;
fn add(self, rhs: Rhs) -> Self::Output;
}
With associated types, callers write impl Iterator<Item = i32> instead of Iterator<i32>. The constraint is that a type can only be an Iterator of one Item — you cannot implement Iterator twice with different Item types.
With generic parameters (like Add<Rhs>), a single type can implement the trait multiple times with different type arguments. Traits also power the derive macro system, which auto-generates common trait implementations like Debug, Clone, and PartialEq.
- The Rust Book: Traits — definitions, bounds, default implementations
- The Rust Book: Generics — generic functions, structs, enums
- The Rust Book: Advanced Traits — associated types, UFCS, supertraits
- Rust By Example: Traits — practical trait patterns
- Rust Reference: Object Safety — when dyn Trait is allowed
When to Use Rust Traits & Generics: Trait Bounds, impl Trait, Turbofish
- Define traits to specify shared behavior across types.
- Use generics to write code that works with multiple types.
- Use trait bounds to constrain generic types to those with certain behavior.
- Use `impl Trait` for simple return types that implement a trait.
- Use where clauses when bounds get complex.
Check Your Understanding: Rust Traits & Generics: Trait Bounds, impl Trait, Turbofish
What is the difference between generics and trait objects?
Generics are monomorphized at compile time, so zero runtime cost but larger binary. Trait objects (`dyn Trait`) use dynamic dispatch at runtime, so smaller binary but slight overhead. Use generics when you know types at compile time.
What You'll Practice: Rust Traits & Generics: Trait Bounds, impl Trait, Turbofish
Common Rust Traits & Generics: Trait Bounds, impl Trait, Turbofish Pitfalls
- `error[E0277]` when calling a method on a generic `T` -- the compiler does not know `T` has that method. Add a trait bound: `T: Display`.
- `error[E0117]` when implementing a foreign trait on a foreign type -- Rust's orphan rule prevents conflicting implementations across crates. Wrap the foreign type in a newtype: `struct Wrapper(ForeignType)`.
- `error[E0046]` not all trait items implemented -- your `impl` block is missing required methods. Only methods with default implementations can be omitted.
- Two `impl Trait` arguments accept different concrete types -- `fn foo(a: impl Trait, b: impl Trait)` desugars to two separate generics, not one. If they must be the same type, use an explicit generic: `fn foo<T: Trait>(a: T, b: T)`.
- Return-position `impl Trait` fails with conditional return types -- the function must return exactly one concrete type. Use `Box<dyn Trait>` when you need to return different types from different branches.
- Calling `.parse()` or `.collect()` without a type hint -- the compiler cannot infer the target type. Use the turbofish: `"42".parse::<i32>()` or `iter.collect::<Vec<_>>()`.
- Trying to use a trait as `dyn Trait` but getting 'not object safe' -- the trait has methods returning `Self` or lacking a receiver. Either restructure the trait or use generics instead.
Rust Traits & Generics: Trait Bounds, impl Trait, Turbofish FAQ
What are trait bounds?
Constraints on generic types: `fn foo<T: Clone>(x: T)` means T must implement Clone. Without bounds, you can only use methods available on all types.
When should I use where clauses?
When bounds are complex or numerous. `fn foo<T, U>(x: T, y: U) where T: Clone + Debug, U: Display` is clearer than cramming everything after the function name.
What is impl Trait?
In argument position, it is shorthand for a generic (`impl Summary` = `T: Summary`). In return position, it hides the concrete type. Return-position `impl Trait` must return exactly one concrete type — you cannot conditionally return different types.
What is the turbofish?
The `::<>` syntax for specifying generic types: `"42".parse::<i32>()`. Named for its fish-like appearance. Used when type inference can't figure out the type.
What are associated types?
Types defined inside traits: `type Item;` in Iterator. Each type implements the trait once with one choice of associated type. Use generic parameters (`trait Add<Rhs>`) when a type should implement the trait multiple times with different types.
What is object safety?
A trait is object-safe (usable as `dyn Trait`) if its methods all have a receiver (`&self`, `&mut self`) and don't return `Self`. Traits like `Clone` are not object-safe because `clone()` returns `Self`.
What is the difference between generics and trait objects?
Generics (`T: Trait`) are monomorphized at compile time — zero runtime cost but larger binary. Trait objects (`dyn Trait`) use vtable dispatch at runtime — flexible (heterogeneous collections) but slight overhead. Use generics when you know types at compile time.
Rust Traits & Generics: Trait Bounds, impl Trait, Turbofish Syntax Quick Reference
trait Summary {
fn summarize(&self) -> String;
}impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{} by {}", self.headline, self.author)
}
}trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}fn notify<T: Summary>(item: &T) {
println!("Breaking! {}", item.summarize());
}fn notify<T: Summary + Display>(item: &T) {
println!("{}", item);
}fn some_function<T, U>(t: &T, u: &U) -> i32
where
T: Display + Clone,
U: Clone + Debug,
{ 0 }fn make_iter() -> impl Iterator<Item = i32> {
vec![1, 2, 3].into_iter()
}let shapes: Vec<Box<dyn Shape>> = vec![
Box::new(Circle { r: 1.0 }),
Box::new(Square { s: 2.0 }),
];let number = "42".parse::<i32>().unwrap();<Human as Pilot>::fly(&h);trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}#[derive(Debug, Clone, PartialEq)]
struct Point {
x: i32,
y: i32,
}Rust Traits & Generics: Trait Bounds, impl Trait, Turbofish Sample Exercises
Fill in the trait name to implement it for Person.
GreetFill in the trait bound required to call clone().
CloneFill in the associated type reference for the return value.
Self::Item+ 48 more exercises
Copy-ready syntax examples for quick lookup