Skip to content

Rust Traits

A trait defines a set of behavior that a type can implement — a bit like an interface in other languages. It’s how Rust describes “this type can do X,” and it’s what powers the trait bounds you saw on the Generics page.

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

This says: any type that implements Speak must provide a speak method that returns a String. On its own, a trait is just a promise — it doesn’t do anything until some type implements it.

trait Speak {
fn speak(&self) -> String;
}
struct Dog;
struct Cat;
impl Speak for Dog {
fn speak(&self) -> String {
String::from("Woof!")
}
}
impl Speak for Cat {
fn speak(&self) -> String {
String::from("Meow!")
}
}
fn main() {
let dog = Dog;
let cat = Cat;
println!("{}", dog.speak());
println!("{}", cat.speak());
}

Output:

Woof!
Meow!
▶ Try it Yourself

Dog and Cat are unrelated types, but both implement Speak, so both can be called with .speak(). This is the same pattern as impl blocks on the Structs page — impl Speak for Dog just says “here’s how Dog fulfills the Speak trait.”

The real payoff: you can write a function that accepts any type implementing a trait, without caring which specific type it is:

trait Speak {
fn speak(&self) -> String;
}
struct Dog;
impl Speak for Dog {
fn speak(&self) -> String { String::from("Woof!") }
}
fn announce(animal: &impl Speak) {
println!("The animal says: {}", animal.speak());
}
fn main() {
let dog = Dog;
announce(&dog);
}
▶ Try it Yourself

animal: &impl Speak means “any type that implements Speak, I don’t need to know which one.” Add a Bird type that also implements Speak, and announce works with it too, with no changes.

Remember #[derive(Debug)] from the Structs page? Debug is itself a trait — derive just auto-generates the implementation for you instead of you writing it by hand. Other common ones you’ll run into: Clone (for .clone()), PartialEq (for ==), and PartialOrd (for <, >, which you saw used as a trait bound on the Generics page).

A trait can provide a default method body, which implementing types can use as-is or override:

trait Greet {
fn name(&self) -> String;
fn greet(&self) -> String {
format!("Hello, {}!", self.name())
}
}

Any type implementing Greet only has to define name() — greet() comes for free, unless it chooses to override it too.

There’s more to traits than this page covers. The Common Traits page walks through the standard ones you’ll use every day, and Advanced Traits covers associated types, supertraits and other tools you’ll see in bigger codebases.

Define a trait Area with one method, fn area(&self) -> f64. Implement it for a Rectangle (with width and height) and a Square (with side). Print the area of a 3 by 4 rectangle and a square with side 5. Hint: each impl Area for ... block only needs the one method, and its body is a single multiplication.

Show solution
trait Area {
fn area(&self) -> f64;
}
struct Rectangle {
width: f64,
height: f64,
}
struct Square {
side: f64,
}
impl Area for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
}
impl Area for Square {
fn area(&self) -> f64 {
self.side * self.side
}
}
fn main() {
let rect = Rectangle { width: 3.0, height: 4.0 };
let square = Square { side: 5.0 };
println!("Rectangle: {}", rect.area());
println!("Square: {}", square.area());
}

Output:

Rectangle: 12
Square: 25
▶ Try it Yourself

Define a trait Describe with fn describe(&self) -> String. Implement it for a Book (with a title) and a Movie (with a title and minutes). Then write one function fn show(item: &impl Describe) that prints the description, and call it with both. Hint: use format! to build each description string.

Show solution
trait Describe {
fn describe(&self) -> String;
}
struct Book {
title: String,
}
struct Movie {
title: String,
minutes: u32,
}
impl Describe for Book {
fn describe(&self) -> String {
format!("Book: {}", self.title)
}
}
impl Describe for Movie {
fn describe(&self) -> String {
format!("Movie: {} ({} min)", self.title, self.minutes)
}
}
fn show(item: &impl Describe) {
println!("{}", item.describe());
}
fn main() {
let book = Book { title: String::from("The Hobbit") };
let movie = Movie { title: String::from("Up"), minutes: 96 };
show(&book);
show(&movie);
}

Output:

Book: The Hobbit
Movie: Up (96 min)
▶ Try it Yourself

Take the Greet trait from this page, with its default greet() method. Implement it for a Person, which keeps the default, and for a Pirate, which overrides greet() to say Ahoy, ...! instead. Hint: both types must define name(). Only Pirate also writes its own greet().

Show solution
trait Greet {
fn name(&self) -> String;
fn greet(&self) -> String {
format!("Hello, {}!", self.name())
}
}
struct Person {
name: String,
}
struct Pirate {
name: String,
}
impl Greet for Person {
fn name(&self) -> String {
self.name.clone()
}
}
impl Greet for Pirate {
fn name(&self) -> String {
self.name.clone()
}
fn greet(&self) -> String {
format!("Ahoy, {}!", self.name())
}
}
fn main() {
let ana = Person { name: String::from("Ana") };
let jack = Pirate { name: String::from("Jack") };
println!("{}", ana.greet());
println!("{}", jack.greet());
}

Output:

Hello, Ana!
Ahoy, Jack!
▶ Try it Yourself
  • A trait defines behavior a type can implement, similar to an interface.
  • impl TraitName for Type provides the implementation.
  • &impl TraitName as a parameter type accepts any type implementing that trait.
  • Traits can supply default method bodies that implementers can use or override.
  • Debug, Clone, and PartialEq are common traits from the standard library — #[derive(...)] implements them automatically.

Quick check

1. What does a trait define?

2. What does animal: &impl Speak as a parameter type mean?

3. What is #[derive(Debug)] actually doing?

4. What is a default implementation in a trait?

5. Which of these is a trait, not a struct or enum, according to this page?

Score: 0 / 5