Traits

What Is a Trait

A trait is a language construct in Rust that defines a shared behavioral contract—a named collection of method signatures, associated functions, associated types, and associated constants—that concrete types can implement.

Behavioral Contracts Without Class Inheritance

Rust does not use class-based inheritance (class Dog extends Animal). Instead, data layout (struct and enum) is completely separated from shared behavior (trait):

Struct / Enum (Data Layout)         Trait (Behavioral Contract)
+-------------------------+         +-----------------------------------+
| struct Circle { r: f64 }| ----+   | trait Area {                      |
+-------------------------+     +-> |     fn area(&self) -> f64;        |
| struct Square { s: f64 }| ----+   | }                                 |
+-------------------------+         +-----------------------------------+

Any type—whether a primitive (i32), a standard library collection (Vec<T>), a user-defined struct or enum, a reference (&T), or a unit struct—can implement a trait to declare that it supports that behavior:

trait Area {
    fn area(&self) -> f64;
}

struct Circle {
    radius: f64,
}

struct Square {
    side: f64,
}

impl Area for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }
}

impl Area for Square {
    fn area(&self) -> f64 {
        self.side * self.side
    }
}

fn print_area<T: Area>(shape: &T) {
    println!("Area: {:.2}", shape.area());
}

fn main() {
    let c = Circle { radius: 2.0 };
    let s = Square { side: 3.0 };

    print_area(&c);
    print_area(&s);
}
Area: 12.57
Area: 9.00

Core Roles of Traits in Rust

Traits serve four structural roles across the language:

Role Example What It Enables
Shared method interface Iterator::next(&mut self) Uniform method calls across disparate types
Generic constraint (trait bound) fn sort<T: Ord>(slice: &mut [T]) Compile-time verification that T supports required operations
Operator and language hooks std::ops::Add, Drop, Deref Customizing +, automatic cleanup, and smart-pointer dereferencing
Marker capability Copy, Send, Sync, Sized Compile-time guarantees about memory copying, thread safety, or size with zero methods

Explicit Opt-In vs. Structural ("Duck") Typing

Unlike Go interfaces or TypeScript structural types—where any type with matching method names satisfies an interface implicitly—Rust traits are nominal and explicit. A type implements a trait only when an explicit impl Trait for Type block (or #[derive(Trait)]) exists:

trait Printable {
    fn label(&self) -> &str;
}

struct Badge {
    title: String,
}

impl Badge {
    // Inherent method has the exact same signature as `Printable::label`,
    // but `Badge` does NOT implement `Printable` without `impl Printable for Badge`.
    fn label(&self) -> &str {
        &self.title
    }
}

fn main() {
    let b = Badge {
        title: String::from("Admin"),
    };
    println!("Inherent call: {}", b.label());
}
Inherent call: Admin

Trait Visibility and Scoping Rule

To call a trait method on a value (value.trait_method()), the trait itself must be in scope in the current module. Traits in the standard prelude (Clone, Copy, Debug, Drop, Fn, From, Into, Iterator, PartialEq, PartialOrd, ToOwned, ToString) are imported automatically; all other traits must be brought into scope with use:

// Bring `std::io::Write` into scope so `Vec<u8>::write_all` can be called:
use std::io::Write;

fn main() {
    let mut buffer: Vec<u8> = Vec::new();
    buffer.write_all(b"trait in scope").unwrap();

    println!("{}", String::from_utf8_lossy(&buffer));
}
trait in scope