Rust Traits Shared Behavior
A trait defines a set of behaviors that a type can have. Any type that implements a trait promises to provide the functions listed in that trait. Traits are similar to interfaces in other languages — they define what a type can do, not what it contains.
Defining a Trait
trait Greet {
fn say_hello(&self) -> String;
}
This trait has one requirement: any type that implements Greet must provide a say_hello method that returns a String.
The Job Contract Diagram
trait Driver {
fn drive(&self); ← Must be able to drive
}
Any type that signs the Driver contract must provide the drive() function.
Bus, Car, Truck can all implement Driver in their own way.
Implementing a Trait
struct Dog {
name: String,
}
struct Cat {
name: String,
}
impl Greet for Dog {
fn say_hello(&self) -> String {
format!("Woof! I am {}.", self.name)
}
}
impl Greet for Cat {
fn say_hello(&self) -> String {
format!("Meow! I am {}.", self.name)
}
}
fn main() {
let dog = Dog { name: String::from("Rex") };
let cat = Cat { name: String::from("Luna") };
println!("{}", dog.say_hello());
println!("{}", cat.say_hello());
}
Output:
Woof! I am Rex. Meow! I am Luna.
Default Implementations
A trait can provide a default implementation for a method. Types can use the default or override it:
trait Summary {
fn title(&self) -> String;
fn preview(&self) -> String {
format!("Read more: {}", self.title()) ← Default uses title()
}
}
struct Article {
headline: String,
}
impl Summary for Article {
fn title(&self) -> String {
self.headline.clone()
}
// preview() uses the default — no need to implement it
}
Traits as Function Parameters
Use impl TraitName in a function signature to accept any type that implements the trait:
fn announce(item: &impl Summary) {
println!("Breaking news: {}", item.title());
}
This function works with any type that implements Summary — you do not need a separate version for every type.
Trait Bounds with Where
For multiple traits or more complex constraints, the where clause keeps the signature readable:
fn compare<T>(a: &T, b: &T) -> bool
where
T: PartialOrd + std::fmt::Display,
{
println!("Comparing {} and {}", a, b);
a < b
}
Common Standard Library Traits
Display — Custom Print Formatting
use std::fmt;
struct Point {
x: i32,
y: i32,
}
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
fn main() {
let p = Point { x: 3, y: 7 };
println!("{}", p); ← (3, 7)
}
Clone and Copy
Adding #[derive(Clone, Copy)] above a struct automatically implements these traits. Copy types are duplicated instead of moved on assignment.
#[derive(Clone, Copy, Debug)]
struct Color {
r: u8,
g: u8,
b: u8,
}
PartialEq — Equality Comparison
#[derive(PartialEq)]
struct Point { x: i32, y: i32 }
let a = Point { x: 1, y: 2 };
let b = Point { x: 1, y: 2 };
println!("{}", a == b); ← true
Returning Traits from Functions
Use impl TraitName as a return type when you want to return some type that implements the trait, without specifying the exact type:
fn make_greeting() -> impl Greet {
Dog { name: String::from("Buddy") }
}
The derive Macro
Many standard traits can be automatically added to your type using the #[derive] attribute. The compiler generates the implementation for you:
#[derive(Debug, Clone, PartialEq)]
struct User {
name: String,
age: u32,
}
Quick Reference
trait Name { fn method(&self); } ← Define a trait
impl Trait for Type { fn method() { } } ← Implement for a type
fn f(x: &impl Trait) { } ← Accept any impl of Trait
#[derive(Debug, Clone)] ← Auto-implement common traits
