Traits in Rust: defining shared behavior
How Rust traits work: defining a trait, implementing it for your types, default methods, trait bounds, impl Trait, and deriving standard traits.
A trait names a set of methods that types can implement. It's how Rust expresses "anything that can do X", similar to interfaces in Java, C# or Go.
Defining and implementing a trait
trait Describe {
fn name(&self) -> String;
fn describe(&self) -> String {
format!("This is {}.", self.name())
}
}
struct Sword {
damage: u32,
}
struct Potion;
impl Describe for Sword {
fn name(&self) -> String {
format!("a sword ({} dmg)", self.damage)
}
}
impl Describe for Potion {
fn name(&self) -> String {
String::from("a potion")
}
fn describe(&self) -> String {
String::from("A potion. Drink it.")
}
}
fn main() {
println!("{}", Sword { damage: 12 }.describe());
println!("{}", Potion.describe());
}
This is a sword (12 dmg).
A potion. Drink it.
- The trait lists method signatures.
namehas no body, so every implementor must write it. describehas a default implementation, which implementors get for free or can override (asPotiondoes).impl Describe for Swordimplements the trait for one type.
Using traits: bounds and impl Trait
Write functions that accept any type implementing a trait:
fn announce(item: &impl Describe) { println!("{}", item.describe()); }
fn announce<T: Describe>(item: &T) { println!("{}", item.describe()); } // the same, longer form
impl Trait in a return position means "some type that implements this trait, which I won't name": fn make_counter() -> impl Iterator<Item = u32>. It's how you return closures and iterator chains.
For a list holding different types that share a trait, use a trait object: Vec<Box<dyn Describe>>.
Traits you'll derive or implement constantly
| Trait | Gives you | How |
|---|---|---|
Debug |
{:?} printing |
#[derive(Debug)] |
Clone, Copy |
.clone(), implicit copies |
derive |
PartialEq, Eq |
==, != |
derive |
PartialOrd, Ord |
<, >, sort() |
derive (compares fields in order) |
Hash |
use as a HashMap key |
derive, with Eq |
Default |
Type::default() |
derive or implement |
Display |
{} printing, .to_string() |
implement by hand |
From |
Type::from(x), .into() |
implement by hand |
The orphan rule
You can implement a trait for a type only if you own the trait or the type (one of them is defined in your crate). You can implement your Describe for String, or Display for your Sword, but not Display for Vec<i32>. Wrap the foreign type in a newtype (struct Scores(Vec<i32>);) to get around it.
Common mistakes
- Calling a trait method without the trait in scope. Trait methods are only callable when the trait is imported. The error is E0599, "no method named
areafound", with a help line naming the trait touse. This is why you writeuse std::io::Write;before callingwrite_all, oruse std::fmt::Write;beforewrite!into aString. - Missing a required method: E0046, "not all trait items implemented, missing:
name". - Expecting fields in traits. Traits only have methods (and associated types and constants). Use a getter method.
Example
use std::fmt;
trait Shape {
fn area(&self) -> f64;
fn name(&self) -> &'static str;
fn summary(&self) -> String {
format!("{} with area {:.2}", self.name(), self.area())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
struct Rect {
w: f64,
h: f64,
}
#[derive(Debug, Clone, Copy, PartialEq)]
struct Circle {
r: f64,
}
impl Shape for Rect {
fn area(&self) -> f64 {
self.w * self.h
}
fn name(&self) -> &'static str {
"rectangle"
}
}
impl Shape for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.r * self.r
}
fn name(&self) -> &'static str {
"circle"
}
fn summary(&self) -> String {
format!("circle of radius {}", self.r)
}
}
impl fmt::Display for Rect {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}x{}", self.w, self.h)
}
}
fn print_summary(s: &impl Shape) {
println!("{}", s.summary());
}
fn bigger<'a, T: Shape>(a: &'a T, b: &'a T) -> &'a T {
if a.area() >= b.area() { a } else { b }
}
fn main() {
let r = Rect { w: 3.0, h: 4.0 };
let c = Circle { r: 1.0 };
print_summary(&r);
print_summary(&c);
println!("bigger: {}", bigger(&r, &Rect { w: 5.0, h: 1.0 }));
println!("default {:?}, equal {}", Rect::default(), r == Rect { w: 3.0, h: 4.0 });
}
Output:
rectangle with area 12.00
circle of radius 1
bigger: 3x4
default Rect { w: 0.0, h: 0.0 }, equal true