Lesson 13

Traits

Define shared behavior with traits -- Rust's powerful system for polymorphism and abstraction.

What Are Traits?

Traits define shared behavior -- they're similar to interfaces in other languages. A trait says "any type that implements me must provide these methods." This lets you write code that works with many different types, as long as they share the behavior you need.

trait Summary {
    fn summarize(&self) -> String;
}

Implementing Traits

To implement a trait for a type, use impl TraitName for TypeName. You must provide concrete implementations for all required methods. Traits can also have default implementations -- methods with a body that types get for free unless they override them.

Traits as Parameters

The real power of traits is using them as function parameters. With impl Trait syntax, you can write functions that accept any type implementing that trait. This is how Rust achieves polymorphism without inheritance. The standard library is full of common traits like Display, Debug, Clone, and PartialEq that you'll use constantly.

Try it

// Define a trait with a required method and a default method
trait Summary {
    fn summarize(&self) -> String;

    // Default implementation -- types get this for free
    fn preview(&self) -> String {
        format!("Read more: {}", self.summarize())
    }
}

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

struct Tweet {
    username: String,
    body: String,
}

// Implement Summary for Article
impl Summary for Article {
    fn summarize(&self) -> String {
        format!("{} by {}", self.title, self.author)
    }
}

// Implement Summary for Tweet
impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("@{}: {}", self.username, self.body)
    }

    // Override the default preview
    fn preview(&self) -> String {
        format!("Tweet from @{}", self.username)
    }
}

// A function that accepts anything implementing Summary
fn print_summary(item: &impl Summary) {
    println!("  Summary: {}", item.summarize());
    println!("  Preview: {}", item.preview());
}

fn main() {
    let article = Article {
        title: String::from("Rust is Amazing"),
        author: String::from("Jane Doe"),
        content: String::from("Rust combines performance with safety..."),
    };

    let tweet = Tweet {
        username: String::from("rustlang"),
        body: String::from("Rust 2024 edition is here!"),
    };

    println!("--- Article ---");
    println!("  Content: {}", article.content);
    print_summary(&article);

    println!("\n--- Tweet ---");
    print_summary(&tweet);

    // The default preview method uses summarize()
    println!("\n--- Default vs Overridden ---");
    println!("  Article preview (default): {}", article.preview());
    println!("  Tweet preview (custom):    {}", tweet.preview());
}

Challenge

Add a Display implementation for the Article struct so you can print it directly with println!("{}"). The Display trait requires a fmt method that uses write!(f, "...").

Previous: Collections

Next: Generics