Why this matters
Generic functions need to know what they can do with a type: compare it, print it, compute its area. Different types also often share behavior: circles and rectangles both have an area; integers, strings and your own structs can all be printed. A trait names a set of methods that types can implement. Traits are Rust's interfaces, and they are everywhere: Display, Debug, Clone, Iterator, From, Drop are all traits you have already met. This lesson shows how to define and implement them; the next shows how to write code that works with any type implementing a trait.
Defining a trait
trait Describe {
fn name(&self) -> String;
fn legs(&self) -> u32;
}
struct Dog;
struct Bird {
species: String,
}
impl Describe for Dog {
fn name(&self) -> String {
String::from("dog")
}
fn legs(&self) -> u32 {
4
}
}
impl Describe for Bird {
fn name(&self) -> String {
format!("bird ({})", self.species)
}
fn legs(&self) -> u32 {
2
}
}
fn main() {
let d = Dog;
let b = Bird { species: String::from("robin") };
println!("{} has {} legs", d.name(), d.legs());
println!("{} has {} legs", b.name(), b.legs());
}
dog has 4 legs
bird (robin) has 2 legs
trait Name { ... }lists method signatures ending in;. These are the required methods.impl Trait for Type { ... }provides the bodies for one type. It must implement every required method, with exactly matching signatures.- Once implemented, the methods are called like any other method.
Forgetting a method is an error that lists what is missing:
trait Describe {
fn name(&self) -> String;
fn legs(&self) -> u32;
}
struct Snake;
impl Describe for Snake {
fn name(&self) -> String {
String::from("snake")
}
}
fn main() {
println!("{}", Snake.name());
}
error[E0046]: not all trait items implemented, missing: `legs`
--> main.rs:8:1
|
3 | fn legs(&self) -> u32;
| ---------------------- `legs` from trait
...
8 | impl Describe for Snake {
| ^^^^^^^^^^^^^^^^^^^^^^^ missing `legs` in implementation
Default methods
A trait can provide default implementations, which types get for free and may override:
trait Describe {
fn name(&self) -> String;
fn legs(&self) -> u32;
fn summary(&self) -> String {
format!("a {} with {} legs", self.name(), self.legs())
}
}
struct Dog;
struct Fish;
impl Describe for Dog {
fn name(&self) -> String { String::from("dog") }
fn legs(&self) -> u32 { 4 }
}
impl Describe for Fish {
fn name(&self) -> String { String::from("fish") }
fn legs(&self) -> u32 { 0 }
fn summary(&self) -> String {
String::from("a fish, which swims instead")
}
}
fn main() {
println!("{}", Dog.summary());
println!("{}", Fish.summary());
}
a dog with 4 legs
a fish, which swims instead
Default methods can call the required ones. This pattern is powerful: the standard Iterator trait has one required method (next) and dozens of default methods (map, filter, sum, count...) built on it. Implement next for your type and you get all of them (module 11).
Implementing standard traits
Your types become first-class citizens by implementing standard traits. Display (what {} uses) is the most common one written by hand:
use std::fmt;
struct Point {
x: i32,
y: i32,
}
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
fn main() {
let p = Point { x: 3, y: -1 };
println!("p = {p}");
let s = p.to_string();
println!("{s:>12}|");
}
p = (3, -1)
(3, -1)|
Implementing Display also gives you to_string() for free: the standard library implements another trait, ToString, for every type that implements Display. This is called a blanket implementation, and it is another way traits compose. (Width and alignment like {:>12} apply to the result of to_string; inside fmt, f.pad(...) would honor them directly.)
Traits with associated functions and constants
A trait can require functions without self (called as Type::function()) and constants:
trait Shape {
const SIDES: u32;
fn unit() -> Self;
fn area(&self) -> f64;
}
struct Square(f64);
impl Shape for Square {
const SIDES: u32 = 4;
fn unit() -> Self {
Square(1.0)
}
fn area(&self) -> f64 {
self.0 * self.0
}
}
fn main() {
let s = Square::unit();
println!("{} sides, area {}", Square::SIDES, s.area());
}
4 sides, area 1
Self in a trait means "the implementing type". Default::default() and From::from() are standard examples of trait functions without self.
Where impls may live: the orphan rule
You can implement:
- your trait for your type,
- your trait for a foreign type (e.g. your
Describefori32), - a foreign trait for your type (e.g.
Displayfor yourPoint),
but not a foreign trait for a foreign type (Display for Vec<i32>). Otherwise two crates could provide conflicting implementations. The workaround is a newtype wrapper (struct Report(Vec<i32>)) for which you can implement anything.
trait Double {
fn double(&self) -> Self;
}
impl Double for i32 {
fn double(&self) -> Self {
self * 2
}
}
impl Double for String {
fn double(&self) -> Self {
self.repeat(2)
}
}
fn main() {
println!("{} {}", 21.double(), String::from("ab").double());
}
42 abab
Extending existing types with your own traits ("extension traits") is a common, legitimate pattern. Note that the trait must be in scope (used) wherever its methods are called.
Common mistakes
- Missing methods in an impl: E0046, which lists them.
- Signature mismatch:
fn legs(&self) -> i32when the trait saysu32is E0053 "methodlegshas an incompatible type for trait". - Calling a trait method without the trait in scope: E0599 "no method named ... found", with help "trait
Xwhich providesmethodis implemented but not in scope; perhaps you want to import it". Add theuse. - Foreign trait for foreign type: E0117 (orphan rule). Use a newtype.
Recap
- A trait declares methods (and possibly functions and constants) a type can implement.
impl Trait for Typeprovides them; every required method must be implemented with a matching signature.- Default methods are inherited and can be overridden; they can call required methods.
- Implementing
Displaygives{}printing andto_string()(via a blanket impl). - Orphan rule: the trait or the type must be local; wrap foreign types in a newtype otherwise.
Going further: read the Rust Book chapter 10.2, "Traits: Defining Shared Behavior". Then implement Display for the Shape enum from module 7.