Shiklo Tools
PDF40
Image20
Calculators & Converters25
Text & Writing13
Developer26
Video14
Audio14
Document14
Data & Spreadsheet12
Web & SEO9
OCR & Scanning5
Archive & File12
Security & Privacy23
Health & Medical29
QR & Barcode5

Rust Traits

Very different things can share an ability. A dog, a car alarm and a phone can all make a sound. A circle and a square both have an area. In Rust, a trait describes such an ability: a set of methods that a type promises to have. Any type can sign up for a trait by implementing those methods.

If you know other languages, traits are close to what they call interfaces.

Defining and implementing a trait

trait Speak {
    fn sound(&self) -> String;
}

struct Dog;
struct Cat;

impl Speak for Dog {
    fn sound(&self) -> String {
        String::from("Woof")
    }
}

impl Speak for Cat {
    fn sound(&self) -> String {
        String::from("Meow")
    }
}

fn main() {
    println!("The dog says {}", Dog.sound());
    println!("The cat says {}", Cat.sound());
}
Output
The dog says Woof
The cat says Meow

The trait lists the method's name and types, without a body. Each impl Speak for ... block then provides the body for one type. struct Dog; is a struct with no fields at all, which is perfectly allowed.

Functions that accept any type with a trait

The real power appears when a function accepts any type that has the trait. impl Speak in a parameter means "anything that can speak":

trait Area {
    fn area(&self) -> f64;
    fn describe(&self) -> String {
        format!("a shape with area {:.1}", self.area())
    }
}

struct Circle { radius: f64 }
struct Square { side: f64 }

impl Area for Circle {
    fn area(&self) -> f64 { 3.14159 * self.radius * self.radius }
}

impl Area for Square {
    fn area(&self) -> f64 { self.side * self.side }
    fn describe(&self) -> String {
        format!("a square of side {}", self.side)
    }
}

fn report(shape: &impl Area) {
    println!("This is {}", shape.describe());
}

fn main() {
    report(&Circle { radius: 2.0 });
    report(&Square { side: 3.0 });
}
Output
This is a shape with area 12.6
This is a square of side 3

describe has a body in the trait itself. That is a default method: types get it for free, and may replace it with their own version, as Square does here.

Display: teaching Rust how to print your type

println!("{}", value) works only for types that implement the standard trait std::fmt::Display. Implement it, and your own types print beautifully:

use std::fmt;

struct Temperature {
    celsius: f64,
}

impl fmt::Display for Temperature {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:.1}°C", self.celsius)
    }
}

fn main() {
    let today = Temperature { celsius: 31.25 };
    println!("Today it is {}", today);
    let text = today.to_string();
    println!("As text: {} ({} characters)", text, text.chars().count());
}
Output
Today it is 31.2°C
As text: 31.2°C (6 characters)

Notice the bonus: implementing Display also gives you to_string() for free. Many traits in Rust work like this, building on each other.

Traits you can derive

Some common traits can be written automatically with #[derive(...)]: Debug for {:?}, Clone for .clone(), PartialEq for ==, and PartialOrd for < and >. Deriving is quick, and you can always write the impl by hand when you need something special.

Practice exercises

  1. Add a Cow type to the first example and make it say "Moo".
  2. Add a Rectangle to the Area example, with its own describe.
  3. Implement Display for a Point { x: i32, y: i32 } so it prints as (3, 4).