← Back to Rust Course | Chapter 11: Generics & Traits | Lesson 3 of 7

Trait Basics

A trait is a promise that says "any type using me knows how to do these particular things."

Defining a Trait

The trait keyword defines a set of method signatures that any implementing type must provide, describing shared behavior without dictating how it's stored.

Example: Defining a Trait

markup
trait Greet {
    fn greet(&self) -> String;
}

struct Person {
    name: String,
}

impl Greet for Person {
    fn greet(&self) -> String {
        format!("Hello, my name is {}", self.name)
    }
}

fn main() {
    let p = Person { name: String::from("Ada") };
    println!("{}", p.greet());
}

Implementing a Trait for Multiple Types

The same trait can be implemented for several unrelated types, each providing its own version of the required behavior.

Example: Implementing a Trait for Multiple Types

markup
trait Greet {
    fn greet(&self) -> String;
}

struct Robot;

impl Greet for Robot {
    fn greet(&self) -> String {
        String::from("BEEP BOOP HELLO")
    }
}

fn main() {
    let r = Robot;
    println!("{}", r.greet());
}

A Type Implementing Multiple Traits

A single type can implement as many different traits as needed, each contributing a different piece of behavior to that type.

Example: A Type Implementing Multiple Traits

markup
trait Greet {
    fn greet(&self) -> String;
}

trait Farewell {
    fn farewell(&self) -> String;
}

struct Person {
    name: String,
}

impl Greet for Person {
    fn greet(&self) -> String {
        format!("Hi, {}", self.name)
    }
}

impl Farewell for Person {
    fn farewell(&self) -> String {
        format!("Bye, {}", self.name)
    }
}

fn main() {
    let p = Person { name: String::from("Lee") };
    println!("{}", p.greet());
    println!("{}", p.farewell());
}

Calling Trait Methods Through the Trait

Once a trait is implemented for a type, its methods are called using ordinary dot syntax, exactly like any other method, as long as the trait is in scope.

Example: Calling Trait Methods Through the Trait

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

struct Square {
    side: f64,
}

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

fn main() {
    let sq = Square { side: 4.0 };
    println!("Area: {}", sq.area());
}
Common Mistakes
  1. Forgetting to bring a trait into scope with use before calling its methods on a type, even if the impl exists elsewhere.
  2. Confusing a trait definition with a struct -- traits describe shared behavior, they do not themselves hold data.
  3. Implementing a trait's method with a different signature than the trait declares, which fails to compile.
Chapter Summary
  • A trait defines a set of method signatures that implementing types agree to provide.
  • impl TraitName for TypeName { ... } provides the actual behavior for a specific type.
  • A single type can implement multiple different traits.
  • Traits enable shared behavior across otherwise unrelated types, similar to interfaces in other languages.
🔒

Chapter Quiz — Complete all 7 topics to unlock

0/7 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.