Rust Advanced Traits
On the Traits page you learned how to define a trait and implement it. On the Generics and Common Traits pages you used traits like Display, From, and Add. That is enough for most everyday code. But when you read the standard library, or a popular crate, you’ll see trait features that look a bit strange at first: type Item, Add<Rhs = Self>, trait Pretty: Display, impl<T: Display> ... for T.
This page explains those features one at a time. None of them are magic. Each one solves a small, real problem, and once you know the problem, the syntax makes sense. Take it slowly — it’s fine to read one section, try the code, and come back later.
Associated types: one answer per type
Section titled “Associated types: one answer per type”You’ve already met an associated type: Iterator has type Item. An associated type is a placeholder type inside a trait. Each implementation fills it in once.
Here is a small Container trait. A playlist holds Strings and a score list holds u32s, so each picks its own Item:
use std::fmt::Debug;
trait Container { type Item;
fn get(&self, index: usize) -> Option<&Self::Item>; fn size(&self) -> usize;
fn first(&self) -> Option<&Self::Item> { self.get(0) }}
struct Playlist { songs: Vec<String>,}
struct Scores { values: Vec<u32>,}
impl Container for Playlist { type Item = String;
fn get(&self, index: usize) -> Option<&String> { self.songs.get(index) } fn size(&self) -> usize { self.songs.len() }}
impl Container for Scores { type Item = u32;
fn get(&self, index: usize) -> Option<&u32> { self.values.get(index) } fn size(&self) -> usize { self.values.len() }}
// Works with any Container whose items can be printed with {:?}fn describe<C: Container>(c: &C)where C::Item: Debug,{ println!("{} items, first is {:?}", c.size(), c.first());}
fn main() { let playlist = Playlist { songs: vec![String::from("Intro"), String::from("Outro")] }; let scores = Scores { values: vec![90, 72, 85] };
describe(&playlist); describe(&scores);}Output:
2 items, first is Some("Intro")3 items, first is Some(90)Inside the trait, Self::Item means “whatever type this implementation chose”. C::Item in describe means the same thing for the type C. (The where line is just a tidier place to write the bound — more on that at the end of this page.)
Why not a generic parameter instead? You could write trait Container<T>. But then one type could implement it many times — Container<String> and Container<u32> — and every caller would have to say which one they mean. For a playlist that makes no sense: it holds songs, full stop.
That’s exactly why Iterator uses type Item. A Vec<i32> iterator yields i32 and nothing else, so for x in v.iter() never needs a type annotation. A simple rule of thumb:
- One natural answer per type → associated type (
Iterator::Item,Add::Output). - Many valid answers per type → generic parameter (
From<T>: aStringcan be built from a&str, achar, aBox<str>, …).
Default type parameters and operators
Section titled “Default type parameters and operators”On the Common Traits page you made + work for a Point by implementing Add. Here is how Add is really defined in the standard library:
trait Add<Rhs = Self> { type Output; fn add(self, rhs: Rhs) -> Self::Output;}Two features meet here. Output is an associated type (what the + produces). And Rhs = Self is a default type parameter: “the right-hand side is the same type as the left, unless you say otherwise”. When you write impl Add for Point, you get the default. When you want to add a different type, you write impl Add<OtherType> for Point.
use std::ops::Add;
#[derive(Debug, Clone, Copy)]struct Meters(f64);
#[derive(Debug, Clone, Copy)]struct Centimeters(f64);
// Uses the default Rhs = Self, so this is Meters + Metersimpl Add for Meters { type Output = Meters; fn add(self, other: Meters) -> Meters { Meters(self.0 + other.0) }}
// Overrides the default: Meters + Centimetersimpl Add<Centimeters> for Meters { type Output = Meters; fn add(self, other: Centimeters) -> Meters { Meters(self.0 + other.0 / 100.0) }}
fn main() { let a = Meters(2.0); let b = Meters(3.5); let c = Centimeters(50.0);
println!("{:?}", a + b); println!("{:?}", a + c);}Output:
Meters(5.5)Meters(2.5)Notice that Add uses both tools: a generic parameter for the right-hand side (many valid answers — you can add Meters or Centimeters) and an associated type for the result (one answer for each pair). Default type parameters are mostly useful when you design a trait: they keep the common case short without blocking the rare case.
Supertraits: “you must also implement this”
Section titled “Supertraits: “you must also implement this””Sometimes your trait only makes sense if the type can already do something else. A supertrait says “to implement my trait, you must also implement that one”. You write it with a colon, like a bound:
use std::fmt;
// Anything that is Pretty must also be Displaytrait Pretty: fmt::Display { fn pretty(&self) -> String { format!("*** {} ***", self) }}
struct Temperature { celsius: f64,}
impl fmt::Display for Temperature { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:.1}°C", self.celsius) }}
impl Pretty for Temperature {}
fn main() { let t = Temperature { celsius: 21.456 }; println!("{}", t); println!("{}", t.pretty());}Output:
21.5°C*** 21.5°C ***The default pretty method can use {} on self because the supertrait promises Display is there. Without : fmt::Display, Rust would refuse that format!, since it couldn’t know the type is printable. impl Pretty for Temperature {} is empty — it just says “yes, use the default method”.
Real examples from the standard library: Copy: Clone (every Copy type must also be Clone) and Eq: PartialEq.
Blanket implementations: one impl for many types
Section titled “Blanket implementations: one impl for many types”A blanket implementation implements a trait for every type that meets a condition, in one go. The syntax is impl<T: SomeBound> MyTrait for T:
use std::fmt::Display;
trait Shout { fn shout(&self) -> String;}
// Every type that implements Display gets Shout for freeimpl<T: Display> Shout for T { fn shout(&self) -> String { format!("{}!", self.to_string().to_uppercase()) }}
fn main() { println!("{}", "hello".shout()); println!("{}", 42.shout()); println!("{}", 'a'.shout()); println!("{}", String::from("rust is fun").shout());}Output:
HELLO!42!A!RUST IS FUN!We never wrote impl Shout for i32 or impl Shout for char. The blanket impl covered them all — including Display types that don’t exist yet.
You’ve used a blanket impl many times without knowing it. The standard library has (roughly) impl<T: Display> ToString for T. That’s why every type with Display has a .to_string() method. The same trick gives you Into for free when you implement From.
One limit: once a blanket impl exists, you can’t add a second, more specific impl for a type it already covers (say, a special impl Shout for i32). Rust would have two answers and refuses with a “conflicting implementations” error.
The orphan rule and the newtype workaround
Section titled “The orphan rule and the newtype workaround”Here’s a rule that surprises everyone once. Try to give Vec<String> a nice Display:
use std::fmt;
impl fmt::Display for Vec<String> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "[{}]", self.join(", ")) }}error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crate --> src/main.rs:3:1 |3 | impl fmt::Display for Vec<String> { | ^^^^^^^^^^^^^^^^^^^^^^----------- | | | `Vec` is not defined in the current crate | = note: define and implement a trait or new type insteadThis is the orphan rule: you can implement a trait for a type only if the trait or the type is defined in your crate. Display belongs to the standard library, and so does Vec, so neither is yours.
Why does the rule exist? Imagine two crates you depend on both wrote their own impl Display for Vec<String>. Which one should Rust use? There’s no good answer, so Rust makes the situation impossible.
The workaround is the newtype pattern: wrap the foreign type in a tiny struct of your own. Now the type is yours, and you can implement anything for it:
use std::fmt;
// A local wrapper around a foreign typestruct Shopping(Vec<String>);
impl fmt::Display for Shopping { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "[{}]", self.0.join(", ")) }}
fn main() { let list = Shopping(vec![String::from("milk"), String::from("eggs"), String::from("bread")]); println!("You need: {}", list); println!("That's {} things", list.0.len());}Output:
You need: [milk, eggs, bread]That's 3 thingsself.0 reaches the Vec inside. A newtype has no runtime cost — in memory, Shopping is exactly a Vec<String>. The downside is that the Vec methods aren’t on Shopping directly, so you write list.0.len() (or add the methods you need). Newtypes are useful for much more than the orphan rule; see Design Patterns.
Fully qualified syntax: when names clash
Section titled “Fully qualified syntax: when names clash”Two traits can have methods with the same name, and the type itself can have one too. Which one does robot.greet() call?
trait English { fn greet(&self) -> String; fn language() -> String;}
trait Spanish { fn greet(&self) -> String; fn language() -> String;}
struct Robot;
impl Robot { fn greet(&self) -> String { String::from("BEEP BOOP") }}
impl English for Robot { fn greet(&self) -> String { String::from("Hello!") } fn language() -> String { String::from("English") }}
impl Spanish for Robot { fn greet(&self) -> String { String::from("¡Hola!") } fn language() -> String { String::from("Spanish") }}
fn main() { let r = Robot;
println!("{}", r.greet()); // the type's own method wins println!("{}", English::greet(&r)); // pick a trait by name println!("{}", Spanish::greet(&r));
// No `self` here, so Rust needs the type AND the trait: println!("{}", <Robot as English>::language()); println!("{}", <Robot as Spanish>::language());}Output:
BEEP BOOPHello!¡Hola!EnglishSpanishThree levels, from short to fully explicit:
r.greet()— the type’s own (inherent) method is chosen first.English::greet(&r)— “theEnglishversion”. Rust works out the type from&r.<Robot as English>::language()— fully qualified syntax. You need it for functions with noself, because there’s no value for Rust to look at.English::language()alone fails witherror[E0790]: cannot call associated function on trait without specifying the corresponding impl type.
You won’t need this often. When you do, the compiler error usually suggests the exact fix.
impl Trait in arguments and return values, plus where
Section titled “impl Trait in arguments and return values, plus where”You can write impl Trait in two places, and it means something slightly different in each:
- Argument position —
fn f(x: impl Trait): the caller picks the type. It’s a short way to write a genericfn f<T: Trait>(x: T). - Return position —
fn f() -> impl Trait: the function picks one concrete type and keeps it hidden. The caller only knows “it’s something that implementsTrait”.
// Caller chooses: any iterator of u32 is finefn print_all(items: impl Iterator<Item = u32>) { let all: Vec<u32> = items.collect(); println!("{:?}", all);}
// Function chooses: the real type is a long Filter<...> we never write outfn evens_up_to(limit: u32) -> impl Iterator<Item = u32> { (0..=limit).filter(|n| n % 2 == 0)}
fn main() { print_all(vec![3, 1, 4].into_iter()); print_all(evens_up_to(10));}Output:
[3, 1, 4][0, 2, 4, 6, 8, 10]Return-position impl Trait is especially handy with closures and iterators. The real type of evens_up_to’s result includes the closure’s type, and closures have no name you could type. impl Iterator lets you skip it.
When a function has several generic parameters with several bounds each, the angle brackets get crowded. A where clause moves the bounds after the signature, so the function’s shape is easier to read:
use std::fmt::{Debug, Display};
// Crowded: fn report<T: Display, U: Debug + PartialOrd>(label: T, a: U, b: U) -> String// Tidier:fn report<T, U>(label: T, a: U, b: U) -> Stringwhere T: Display, U: Debug + PartialOrd,{ let bigger = if a > b { a } else { b }; format!("{}: bigger is {:?}", label, bigger)}
fn main() { println!("{}", report("scores", 7, 12)); println!("{}", report(String::from("names"), "ann", "bob"));}Output:
scores: bigger is 12names: bigger is "bob"Both forms mean exactly the same thing to the compiler. where is also the natural place for bounds on associated types, like C::Item: Debug in the first example on this page.
Common mistakes
Section titled “Common mistakes”Implementing a trait but forgetting its supertrait. If you remove the impl fmt::Display for Temperature block from the supertrait example, you get:
error[E0277]: `Temperature` doesn't implement `std::fmt::Display` --> src/main.rs:13:17 |13 | impl Pretty for Temperature {} | ^^^^^^^^^^^ unsatisfied trait bound |note: required by a bound in `Pretty` --> src/main.rs:3:15 | 3 | trait Pretty: fmt::Display { | ^^^^^^^^^^^^ required by this bound in `Pretty`The fix is to implement the supertrait (here, Display) too. The note points right at the : fmt::Display that requires it.
Returning two different types from -> impl Trait. impl Trait hides one type. It can’t be “sometimes this, sometimes that”:
fn numbers(evens: bool) -> impl Iterator<Item = u32> { if evens { (0..10).filter(|n| n % 2 == 0) } else { vec![1, 3, 5].into_iter() }}error[E0308]: `if` and `else` have incompatible types --> src/main.rs:5:9 |2 | / if evens {3 | | (0..10).filter(|n| n % 2 == 0) | | ------------------------------ | | | | | | | the expected closure | | expected because of this4 | | } else {5 | | vec![1, 3, 5].into_iter() | | ^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Filter<Range<{integer}>, {closure@...}>`, found `IntoIter<{integer}>`6 | | } | |_____- `if` and `else` have incompatible types | = note: expected struct `Filter<std::ops::Range<{integer}>, {closure@src/main.rs:3:24: 3:27}>` found struct `std::vec::IntoIter<{integer}>`help: you could change the return type to be a boxed trait object |1 - fn numbers(evens: bool) -> impl Iterator<Item = u32> {1 + fn numbers(evens: bool) -> Box<dyn Iterator<Item = u32>> { |help: if you change the return type to expect trait objects, box the returned expressions |3 ~ Box::new((0..10).filter(|n| n % 2 == 0))4 | } else {5 ~ Box::new(vec![1, 3, 5].into_iter()) |When the type really changes at runtime, return a Box<dyn Trait> instead, as the compiler suggests. See Trait Objects.
- An associated type (
type Item) gives one answer per implementation. Use a generic parameter when a type can implement the trait many ways. - Default type parameters like
Add<Rhs = Self>keep the common case short;impl Add<Other> for Toverrides them. - A supertrait (
trait Pretty: Display) requires another trait, and lets default methods use it. - A blanket impl (
impl<T: Display> MyTrait for T) covers every matching type at once — that’s how.to_string()works. - The orphan rule stops you implementing a foreign trait for a foreign type. Wrap the type in a newtype to get around it.
- Use fully qualified syntax
<Type as Trait>::function()when names clash;impl Traitin return position hides one concrete type;wheremakes long bounds readable.
Quick check
Section titled “Quick check”Quick check
1. Why does Iterator use type Item instead of Iterator<T>?
2. What does Rhs = Self mean in trait Add<Rhs = Self>?
3. You want impl Display for Vec<String>. Rust refuses. What's the usual fix?
4. Which line calls the English version of a function with no self parameter?
5. In fn evens() -> impl Iterator<Item = u32>, who decides the concrete iterator type?
Score: 0 / 5
Read more
Section titled “Read more”- Advanced Traits — The Rust Book — associated types, default type parameters, supertraits, and the newtype pattern, with more examples.
- Traits — The Rust Reference — the precise rules for trait items, supertraits, and associated items.
- Orphan rules — The Rust Reference — the exact definition of when an impl is allowed.
impl Trait— The Rust Reference — how argument-position and return-positionimpl Traitdiffer.- Supertraits — Rust by Example — a short, runnable example of traits that build on other traits.