16.1Introduction to traits

Last updated June 13, 2026

This is the chapter the last one pointed at, and arguably the structural center of the whole language. A trait defines shared behavior, a set of methods, that many different types can implement. If structs and enums (chapters 10 and 11) are how you describe data, traits are how you describe what data can do, in a way that cuts across types. They're also the answer to the cliffhanger from chapter 15: traits are what let a generic function say "T is any type that can be compared," and the largest function finally compiles in lesson 16.3.

Shared behavior across different types

Suppose you have two types, a Tweet and an Article, and you want both to be able to produce a short summary. The summary logic differs (a tweet shows its author, an article its headline), but the capability, "can be summarized", is shared. A trait captures that shared capability:

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

Read this as: "Summary is a behavior; any type that has it must provide a summarize method taking &self and returning a String." The trait declares the method's signature (lesson 13.8: a signature is a contract) but no body, ending in a semicolon. It's a promise of what implementing types will provide, not the provision itself. This is the same idea as the Debug and Default you've been deriving since lesson 10.7: those are traits too, and now you'll see what's underneath them.

Implementing a trait for a type

A type implements a trait by providing the methods the trait requires, in an impl Trait for Type block:

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

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

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

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

fn main() {
    let tweet = Tweet {
        username: String::from("rustlang"),
        content: String::from("traits are great"),
    };
    let article = Article {
        headline: String::from("Rust 2.0 Released"),
        author: String::from("the team"),
    };

    println!("{}", tweet.summarize());
    println!("{}", article.summarize());
}
@rustlang: traits are great
Rust 2.0 Released by the team

impl Summary for Tweet reads "implement the Summary behavior for Tweet," and inside it you write the summarize body that's right for a tweet. The Article implementation does the same with its own logic. Now both types have the Summary behavior, and both expose a summarize() method, even though they're unrelated types with different fields. This is the difference from the plain impl blocks of chapter 10: impl Tweet adds methods that belong to Tweet alone, while impl Summary for Tweet declares that Tweet participates in a shared behavior that other types share too.

Key insight

A trait separates what a type can do from how it does it. The Summary trait says "summarizable things have a summarize method"; each type fills in its own how. This is the foundation for writing code that works with "any summarizable type" without caring which one, the trait-bound generics of lesson 16.3. Traits are how Rust achieves the flexibility that other languages get from inheritance, but built from composition instead (lesson 16.10).

The coherence rule

One rule, stated now because it shapes everything: you can implement a trait for a type only if you define either the trait or the type (or both). You can implement your own Summary trait for the standard library's String (you own the trait), and you can implement the standard library's Display trait for your own Tweet (you own the type). But you cannot implement someone else's trait for someone else's type, say Display for Vec, because both belong to other code. This is the orphan rule (part of coherence), and it exists so that two unrelated libraries can't each define conflicting implementations that break when used together. You'll rarely bump into it, and when you do, the newtype pattern (lesson 10.4) is the standard workaround: wrap the foreign type in your own.

What this chapter replaces

It's worth pausing on scale. Traits do the job that C++ spreads across operator overloading, inheritance, virtual functions, abstract base classes, and template specialization, many chapters of material in a course like learncpp. Rust folds all of it into one feature: a trait, and types that implement it. Over the next ten lessons you'll see traits handle shared behavior (this lesson), default methods, generic bounds, operator overloading (lesson 16.7), conversions (lesson 16.8), and polymorphism (lesson 16.9), each a thing that needed its own large feature elsewhere. This is the chapter where "the Rust course is shorter" is most dramatically earned.

Quiz time

Question #1

What does a trait define, and what does a type do to "have" that trait?

Show solution

A trait defines shared behavior: a set of method signatures (a contract) that implementing types must provide. A type implements the trait by writing an impl Trait for Type block supplying those methods with bodies appropriate to that type. After that, the type has the trait's behavior.

Question #2

What's the difference between impl Tweet { ... } and impl Summary for Tweet { ... }?

Show solution

impl Tweet (chapter 10) adds inherent methods that belong to Tweet alone. impl Summary for Tweet declares that Tweet implements the shared Summary trait, providing the methods that trait requires, behavior that other types can also implement. The first is type-specific; the second participates in a cross-type contract.

Question #3

Can you implement the standard library's Display trait for the standard library's Vec type? Why or why not?

Show solution

No. The orphan rule (coherence) requires that you own either the trait or the type. Both Display and Vec belong to the standard library, so you can implement neither for the other. You could implement Display for your own type, or your own trait for Vec. The workaround for a foreign-trait-on-foreign-type case is to wrap Vec in a newtype you own (lesson 10.4).

A trait can require a method, as Summary does. It can also provide one, a default implementation that types get for free unless they override it. That's the next lesson, and it's the first hint of how traits reduce code.