Exerpad
🔥 1
10 XP
Lesson · Chapter: Traits+10 XP for reading

Shared Behavior

What if different types need to do the same kind of thing? For example, both a Dog and a Cat can "speak", but they make different sounds. In Rust, you define this shared behavior with traits.

Defining a Trait

A trait is like a promise: "Any type that implements this trait must provide these methods."

rustexample — not runnable
trait Greet {
fn hello(&self) -> String;
}

This says: any type with the Greet trait must have a hello method that returns a String.

Implementing a Trait

rust
trait Greet {
fn hello(&self) -> String;
}

struct Robot {
name: String,
}

impl Greet for Robot {
fn hello(&self) -> String {
format!("Beep boop! I am {}", self.name)
}
}

fn main() {
let r = Robot { name: String::from("R2D2") };
println!("{}", r.hello());
}
Output
Press Run to see the output.

The impl Greet for Robot block provides Robot's version of the hello method.

Multiple Types, One Trait

Different types can implement the same trait differently:

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

struct Dog;
struct Cat;

impl Speak for Dog {
fn sound(&self) -> &str {
"Woof!"
}
}

impl Speak for Cat {
fn sound(&self) -> &str {
"Meow!"
}
}

fn main() {
let dog = Dog;
let cat = Cat;
println!("Dog says: {}", dog.sound());
println!("Cat says: {}", cat.sound());
}
Output
Press Run to see the output.

Default Methods

Traits can provide a default implementation that types can use or override:

rust
trait Greet {
fn hello(&self) -> String {
String::from("Hello!")
}
}

struct Friendly;
struct Shy;

impl Greet for Friendly {
fn hello(&self) -> String {
String::from("Hey there, friend!")
}
}

impl Greet for Shy {} // uses the default

fn main() {
let f = Friendly;
let s = Shy;
println!("{}", f.hello());
println!("{}", s.hello());
}
Output
Press Run to see the output.

Trait Parameters

You can write functions that accept any type that implements a trait:

rust
trait Describe {
fn description(&self) -> String;
}

struct Planet {
name: String,
}

impl Describe for Planet {
fn description(&self) -> String {
format!("Planet: {}", self.name)
}
}

fn print_info(item: &impl Describe) {
println!("{}", item.description());
}

fn main() {
let earth = Planet { name: String::from("Earth") };
print_info(&earth);
}
Output
Press Run to see the output.

The &impl Describe means "any type that implements Describe".

Common Derive Traits

Rust has built-in traits you can automatically implement with #[derive(...)]:

rust
#[derive(Debug, Clone, PartialEq)]
struct Point {
x: i32,
y: i32,
}

fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = p1.clone();
println!("{:?}", p1);
println!("Equal: {}", p1 == p2);
}
Output
Press Run to see the output.

Common derive traits:

  • Debug — print with {:?}
  • Clone — create copies with .clone()
  • PartialEq — compare with ==
  • Default — create a default value

The Display Trait

To print your type with {} (not just {:?}), implement the Display trait:

rust
use std::fmt;

struct Color {
r: u8,
g: u8,
b: u8,
}

impl fmt::Display for Color {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "rgb({}, {}, {})", self.r, self.g, self.b)
}
}

fn main() {
let red = Color { r: 255, g: 0, b: 0 };
println!("{}", red);
}
Output
Press Run to see the output.

Summary

ConceptWhat it does
trait Name { fn method(&self); }Defines shared behavior
impl Trait for Type { ... }Implements the trait for a type
Default methodsProvide fallback implementations
&impl TraitAccept any type with that trait
#[derive(Debug, Clone, PartialEq)]Auto-implement common traits
impl fmt::DisplayCustom {} printing

Now let's practice writing and implementing traits!