title: Traits
A trait defines behaviour that a type must have. Think of it as a contract — if a type implements a trait, it guarantees it can do certain things.
If you’re familiar with other languages: traits are similar to interfaces in Java/TypeScript, or protocols in Swift.
Defining a trait
trait Greet {
fn hello(&self) -> String;
}This says: “any type that implements Greet must have a hello method that returns a String.”
Implementing a trait
struct Person {
name: String,
}
struct Robot {
id: u32,
}
impl Greet for Person {
fn hello(&self) -> String {
format!("Hi, I'm {}!", self.name)
}
}
impl Greet for Robot {
fn hello(&self) -> String {
format!("BEEP BOOP. I AM ROBOT {}.", self.id)
}
}Both types now fulfil the Greet contract but with their own behaviour.
Default implementations
A trait can provide a default implementation that types can use or override:
trait Greet {
fn hello(&self) -> String;
fn goodbye(&self) -> String {
String::from("Goodbye!") // default — types can override this if they want
}
}Traits as function parameters
This is where traits get powerful. You can write a function that accepts any type that implements a trait:
fn greet_someone(thing: &impl Greet) {
println!("{}", thing.hello());
}
greet_someone(&Person { name: String::from("Alice") }); // "Hi, I'm Alice!"
greet_someone(&Robot { id: 42 }); // "BEEP BOOP. I AM ROBOT 42."&impl Greet means “a reference to any type that implements Greet.” The function doesn’t care what the type is — only that it can hello().
Traits and generics
impl Trait is shorthand for a generic with a trait bound (see [[generics]]):
// these two are equivalent
fn greet_someone(thing: &impl Greet) { ... }
fn greet_someone<T: Greet>(thing: &T) { ... }The generic form is more flexible when you need to use the type in multiple places or express more complex relationships.
Commonly used standard library traits
You’ll encounter these often in Rust:
| Trait | What it means |
|---|---|
Display | Can be printed with {} |
Debug | Can be printed with {:?} |
Clone | Can be explicitly duplicated with .clone() |
Copy | Can be implicitly copied (stack types like integers) |
PartialOrd | Can be compared with <, > |
PartialEq | Can be compared with ==, != |
Iterator | Can be iterated over with for loops |
You can derive many of these automatically without writing any implementation code:
#[derive(Debug, Clone, PartialEq)]
struct Point {
x: f64,
y: f64,
}
let p = Point { x: 1.0, y: 2.0 };
println!("{:?}", p); // Point { x: 1.0, y: 2.0 }