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.

  • 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.