RustArena

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.

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

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

Practice it