Exerpad
🔥1
10 XP
Lv.1 Beginner
Log In

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

rust
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());
}

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());
}

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());
}

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);
}

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);
}

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);
}

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!