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:

TraitWhat it means
DisplayCan be printed with {}
DebugCan be printed with {:?}
CloneCan be explicitly duplicated with .clone()
CopyCan be implicitly copied (stack types like integers)
PartialOrdCan be compared with <, >
PartialEqCan be compared with ==, !=
IteratorCan 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 }