Traits

Reviewed & published by Brayan K

Rust is a systems programming language focused on speed, memory safety, and fearless concurrency — and traits are how it defines shared behaviour, the foundation of generics and polymorphism.

Part of the free Rust course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

In this lesson you'll define traits, implement them for your types, use default methods, and accept any implementer with impl Trait or dyn Trait.

What You'll Learn in This Lesson

1️⃣ Defining and Implementing Traits

A trait lists methods that implementing types must provide — and may include default implementations. You connect a trait to a type with impl Trait for Type, then call the methods just like any other.

// A trait defines SHARED BEHAVIOUR — a set of methods types can implement.
trait Summary {
    fn summarize(&self) -> String;   // a required method

    // Traits can provide DEFAULT methods too.
    fn preview(&self) -> String {
        format!("(preview) {}", self.summarize())
    }
}

struct Article {
    title: String,
    author: String,
}

struct Tweet {
    user: String,
    text: String,
}

// Implement the trait for each type with impl Trait for Type.
impl Summary for Article {
    fn summarize(&self) -> String {
        format!("{} by {}", self.title, self.author)
    }
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("@{}: {}", self.user, self.text)
    }
    // Tweet keeps the default preview().
}

fn main() {
    let a = Article { title: String::from("Rust 101"), author: String::from("Ada") };
    let t = Tweet { user: String::from("bob"), text: String::from("I love Rust") };
    println!("{}", a.summarize());   // -> Rust 101 by Ada
    println!("{}", t.summarize());   // -> @bob: I love Rust
    println!("{}", t.preview());     // -> (preview) @bob: I love Rust
}

Tweet didn't define preview, so it inherited the trait's default — which itself called the type's own summarize. That layering is what makes traits so composable.

2️⃣ Trait Bounds, impl Trait, and dyn Trait

Traits unlock polymorphism. Writing a parameter as &impl Greet accepts any type implementing Greet with fast static dispatch. To store different implementing types together, use a trait object Box<dyn Greet>, which uses dynamic dispatch.

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

struct English;
struct Spanish;

impl Greet for English { fn hello(&self) -> String { String::from("Hello") } }
impl Greet for Spanish { fn hello(&self) -> String { String::from("Hola") } }

// "impl Trait" as a parameter type accepts ANY type implementing Greet.
fn announce(g: &impl Greet) {
    println!("Greeting: {}", g.hello());
}

// Trait objects let you store DIFFERENT types together behind dyn Trait.
fn announce_all(greeters: &[Box<dyn Greet>]) {
    for g in greeters {
        println!("- {}", g.hello());
    }
}

fn main() {
    announce(&English);          // -> Greeting: Hello
    announce(&Spanish);          // -> Greeting: Hola

    let crowd: Vec<Box<dyn Greet>> = vec![Box::new(English), Box::new(Spanish)];
    announce_all(&crowd);
}

The Vec<Box<dyn Greet>> held both an English and a Spanish value at once — impossible with a plain generic, which fixes a single type. That's the trade: dyn for flexibility, impl for speed.

Your turn. Fill in the blanks marked ___, then run it.

// 🎯 YOUR TURN — implement the trait, then run it.
trait Animal {
    fn sound(&self) -> String;
}

struct Dog;
struct Cat;

// 1) Implement Animal for Dog so sound() returns "Woof".
impl Animal for Dog {
    fn sound(&self) -> String {
        String::from(___)        // 👉 "Woof"
    }
}

// 2) Implement Animal for Cat so sound() returns "Meow".
impl ___ for Cat {              // 👉 the trait name
    fn sound(&self) -> String {
        String::from("Meow")
    }
}

fn main() {
    println!("Dog says {}", Dog.sound());   // expected: Dog says Woof
    println!("Cat says {}", Cat.sound());   // expected: Cat says Meow
}

// ✅ Expected output:
//    Dog says Woof
//    Cat says Meow

Mini-Challenge: A Describable Trait

Define a trait, implement it for two structs, and accept any implementer with impl Trait. Run it with cargo run.

// 🎯 MINI-CHALLENGE: a Describable trait.
//
// 1. Define a trait Describable with one method describe(&self) -> String.
// 2. Define two structs: Book { pages: u32 } and Movie { minutes: u32 }.
// 3. Implement Describable for both:
//      Book   -> format!("A book with {} pages", self.pages)
//      Movie  -> format!("A movie of {} minutes", self.minutes)
// 4. Write fn print_description(item: &impl Describable) that prints describe().
// 5. In main, call it on a Book { pages: 200 } and a Movie { minutes: 90 }.
//
// ✅ Example output:
//    A book with 200 pages
//    A movie of 90 minutes

fn main() {
    // your code here
}

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Traits

GoalRust Syntax
Definetrait T { fn m(&self); }
Implementimpl T for S { }
Default methodfn m(&self) { /* body */ }
Accept any implfn f(x: &impl T)
Trait objectBox<dyn T>
Generic boundfn f<X: T>(x: X)

🎉 Lesson Complete!

Practice quiz

What does a trait define in Rust?

  • A growable list
  • A memory region
  • Shared behaviour as a set of methods
  • A new integer type

Answer: Shared behaviour as a set of methods. A trait declares methods that implementing types must provide, describing shared behaviour.

Which syntax implements a trait Summary for a type Article?

  • impl Summary for Article { }
  • impl Article: Summary { }
  • trait Summary for Article { }
  • Article impl Summary { }

Answer: impl Summary for Article { }. You connect a trait to a type with impl Trait for Type.

What is a default method in a trait?

  • A required method with no body
  • A private helper
  • A method that returns Self
  • A method with a body the trait provides automatically

Answer: A method with a body the trait provides automatically. A trait can supply a complete default body that implementors inherit unless they override it.

A type that uses a trait's default method without overriding it will...

  • Fail to compile
  • Get the trait's provided behaviour for free
  • Return None
  • Panic at runtime

Answer: Get the trait's provided behaviour for free. Default methods reduce boilerplate; you only must implement the required methods.

What does fn announce(g: &impl Greet) accept?

  • Any type that implements Greet, via static dispatch
  • Only the Greet trait itself
  • Any type at all
  • Only Box<dyn Greet>

Answer: Any type that implements Greet, via static dispatch. impl Trait in argument position accepts any implementor with fast static dispatch.

Which lets you store DIFFERENT implementing types together in one Vec?

  • Vec<impl Greet>
  • Vec<Greet>
  • Vec<Box<dyn Greet>>
  • Vec<&Greet>

Answer: Vec<Box<dyn Greet>>. Trait objects boxed as Vec<Box<dyn Greet>> can hold several concrete types at once.

impl Trait uses which kind of dispatch?

  • Dynamic dispatch
  • Static dispatch
  • Runtime reflection
  • Vtable lookup

Answer: Static dispatch. impl Trait is monomorphized like generics, resolving calls at compile time.

The orphan rule says you can implement a trait for a type only if...

  • Both are from the standard library
  • Neither is in your crate
  • The type is a primitive
  • The trait or the type is local to your crate

Answer: The trait or the type is local to your crate. At least one of the trait or the type must be defined in your own crate.

What does #[derive(Debug, Clone, PartialEq)] do?

  • Deletes the type
  • Implements those traits automatically
  • Disables the traits
  • Renames the struct

Answer: Implements those traits automatically. derive auto-generates standard trait implementations for your type.

Error 'not all trait items implemented' usually means...

  • You added an extra method
  • You used impl Trait
  • You skipped a required method
  • You forgot a lifetime

Answer: You skipped a required method. Every required method (one without a default) must be implemented.

Continue this course

Frequently asked questions

What is a trait, and how does it compare to interfaces in other languages?

A trait defines a set of methods that a type can implement, describing shared behaviour. It is similar to an interface in Java or C#, or a protocol in Swift: it says 'any type implementing me must provide these methods'. A key difference is that traits can include default method implementations, and you can implement a trait for types you did not define (including standard-library types), as long as either the trait or the type is local to your crate. Traits are the foundation of how Rust achieves polymorphism.

What is the difference between impl Trait and dyn Trait?

Using impl Trait (for example, fn f(x: &impl Greet)) is static dispatch: the compiler knows the concrete type at compile time and generates specialized, fast code, much like generics. Using dyn Trait (for example, Box<dyn Greet>) is dynamic dispatch: the concrete type is determined at runtime through a pointer to a method table, which adds a tiny indirection cost but lets you store values of different types together, such as in a Vec<Box<dyn Greet>>. Use impl Trait for speed and a single type; use dyn Trait when you need a collection of mixed types.

What are default methods in a trait?

A trait can provide a complete default implementation for some of its methods, so implementing types get that behaviour for free unless they choose to override it. In the example, Summary provides a default preview() built on top of summarize(). Types that implement Summary only have to define summarize(); they inherit preview() automatically, but may replace it if they want different behaviour. Default methods reduce boilerplate and let you add functionality to a trait without breaking existing implementations.

Can I implement a trait for a type I didn't define?

Yes, subject to the orphan rule: you can implement a trait for a type as long as at least one of the trait or the type is defined in your own crate. So you can implement your own trait for a standard type like i32 or String, or implement a standard trait like Display for your own struct. What you cannot do is implement a foreign trait for a foreign type — for example, implementing the standard Display trait for the standard Vec — because that could cause conflicts across crates.