The interface you keep reaching for
Every language you have shipped in had some way to say this type can do X, separate from the type itself. Go has interfaces: a Stringer is anything with a String() string method. Java and C# have interface. TypeScript has structural interfaces. Python leans on duck typing and protocols. The shape is always the same: you name a set of methods, and any type that supplies those methods counts as a member, so code can work against the name instead of against one concrete type.
Rust calls this a trait: a named set of method signatures that a type can promise to fill. The promise is a contract. Define Describe with one method describe, and any type that signs that contract gains a describe method, which means a generic function can ask for "anything that is Describe" and call it. This is the answer to the question the last chapter left open: a bare T can do nothing, but a T: Describe can do exactly what the trait says. Read this top to bottom for shape, then press Run:
// A trait is a shared contract: a set of method signatures a type promises to fill.
trait Describe {
fn describe(&self) -> String;
}
struct Server {
host: String,
port: u16,
}
struct User {
name: String,
admin: bool,
}
// One impl block per type that signs the contract.
impl Describe for Server {
fn describe(&self) -> String {
format!("server at {}:{}", self.host, self.port)
}
}
impl Describe for User {
fn describe(&self) -> String {
let role = if self.admin { "admin" } else { "member" };
format!("user {} ({role})", self.name)
}
}
fn main() {
let s = Server { host: "api".to_string(), port: 8080 };
let u = User { name: "ada".to_string(), admin: true };
println!("{}", s.describe());
println!("{}", u.describe());
}One trait, Describe, names a single method. Two unrelated structs, Server and User, each sign it with their own impl Describe for ... block, and each writes a body that makes sense for its own fields. After that, both values have a .describe() method, and calling it dispatches to the right body for the type in hand. The trait is the noun (the contract); the impl blocks are the signatures on it.
If you know Go interfaces or Java interfaces, you already know the idea: a trait is a set of method signatures a type can promise to provide. The one practical difference to hold onto is that in Rust the type signs the contract explicitly, with an impl Trait for Type block, rather than matching it by accident the way Go's structural interfaces do.
Defining a trait, implementing it for a type
Pull the two halves apart, because Rust keeps them apart on purpose. Defining a trait is writing down the contract: the keyword trait, a name, and a block of method signatures with no bodies. A signature like fn describe(&self) -> String; ends in a semicolon, not a brace, which is how you say "every type that signs this must supply a body". The &self is the value the method is called on, exactly as in an ordinary method from III.07.
Implementing the trait is signing the contract for one specific type, with impl TraitName for TypeName { ... }. Inside, you fill in a body for each required method. Miss one and the program does not compile: the contract is not satisfied, and Rust tells you exactly which method is still owed. The two keywords look almost alike and do opposite jobs, so it is worth fixing in your head now: trait declares the obligation, impl ... for discharges it.
You met impl Server { ... } in III.07 for plain inherent methods, the ones a type just has. Adding for changes the meaning entirely: impl Describe for Server says "Server fulfils the Describe contract". Same keyword, but the for TraitTarget is the tell. Inherent methods belong to the type alone; trait methods are the type keeping a promise that other code can rely on through a bound.
Default methods: the contract can do some of the work
An interface that is all required methods makes every implementer repeat boilerplate. You have felt this: ten types implementing the same interface, each hand-writing the same convenience method on top of the one real method. Rust lets the trait carry that work itself. A trait method may come with a default body, written in the trait, and a type that signs the contract inherits that body for free unless it chooses to override it. Run this:
// A trait can ship default method bodies. A type fills the one required method
// and inherits the rest, or overrides any default it wants to.
trait Greet {
// Required: every type must supply its own name.
fn name(&self) -> String;
// Default: built from name(), free unless a type overrides it.
fn greeting(&self) -> String {
format!("Hello, {}!", self.name())
}
fn shout(&self) -> String {
self.greeting().to_uppercase()
}
}
struct Plain {
who: String,
}
struct Formal {
who: String,
}
impl Greet for Plain {
fn name(&self) -> String {
self.who.clone()
}
// Plain takes both defaults as written.
}
impl Greet for Formal {
fn name(&self) -> String {
self.who.clone()
}
// Formal overrides one default and keeps the other (shout).
fn greeting(&self) -> String {
format!("Good evening, {}.", self.name())
}
}
fn main() {
let p = Plain { who: "Ada".to_string() };
let f = Formal { who: "Grace".to_string() };
println!("{}", p.greeting());
println!("{}", f.greeting());
println!("{}", p.shout());
println!("{}", f.shout());
}Greet has one required method, name (no body, so every type must supply it), and two methods with default bodies, greeting and shout. A default body can call other methods of the same trait: greeting calls self.name(), and shout calls self.greeting(). Plain supplies only name and inherits both defaults as written. Formal overrides greeting with its own wording but still inherits shout, which now calls Formal's greeting because dispatch always lands on the most specific body. You override what you need and keep the rest.
A default method is a body the trait provides so implementers do not have to. Leave the method out of your impl block and you get the default; write the method in your impl block and yours wins. This is how a small contract (one required method) can hand every implementer a rich set of derived methods, the way Iterator gives you dozens of adapters on top of a single next, a story for a later chapter.
Trait bounds: telling a generic what its T can do
Here is where the last chapter and this one meet. In III.09 you wrote functions over a generic T, but a bare T is a type you know nothing about, so you can barely touch it: no methods, no operators, almost nothing. A trait bound fixes that. It is a constraint written as T: Trait that says "T is not just any type, it is some type that signed this contract", and in exchange the function body is allowed to call that trait's methods on the T. Run this:
// A trait bound answers "what can a T do?". T: Summary means T is any type
// that signed the Summary contract, so calling .summary() on it is allowed.
trait Summary {
fn summary(&self) -> String;
}
struct Article {
title: String,
words: u32,
}
struct Tweet {
handle: String,
}
impl Summary for Article {
fn summary(&self) -> String {
format!("\"{}\" ({} words)", self.title, self.words)
}
}
impl Summary for Tweet {
fn summary(&self) -> String {
format!("@{}", self.handle)
}
}
// Generic with a bound: works for every type that is Summary, and the body may
// only use what Summary promises.
fn announce<T: Summary>(item: &T) {
println!("new: {}", item.summary());
}
// impl Trait in argument position is the same thing in shorter clothes.
fn announce_sugar(item: &impl Summary) {
println!("also: {}", item.summary());
}
fn main() {
let a = Article { title: "Traits".to_string(), words: 1200 };
let t = Tweet { handle: "rustlang".to_string() };
announce(&a);
announce(&t);
announce_sugar(&a);
announce_sugar(&t);
}Read fn announce<T: Summary>(item: &T) as: for any type T that is Summary, take a reference to one and announce it. Because the bound promises Summary, the body may call item.summary(); without the bound, that call would not compile, because a bare T has no such method. The one function then works for both Article and Tweet, since both signed the contract. The bound is the bridge: it is how a promise made by the type (the impl) becomes a capability the generic function is allowed to use.
The second function, announce_sugar(item: &impl Summary), does the identical thing in fewer characters. Writing impl Trait in an argument position is shorthand for an anonymous generic with that bound: &impl Summary means "a reference to some type that is Summary", with no name for the type because you never needed one. Reach for the named <T: Summary> form when you must refer to T more than once (two arguments of the same type, or a return type tied to the input); reach for impl Trait when you just need "something that does this" and one mention is enough.
An impl block is a type offering to keep a contract. A bound, T: Summary, is a function requiring it. The compiler checks at every call site that the concrete type you pass actually signed the contract, so a generic over T: Summary can never be handed a type that lacks .summary(). The capability is guaranteed before the code ever runs.
Many bounds with +, and the where clause
Real functions often need a T that can do more than one thing. Maybe you want to both summarize a value and read its price, which means T must satisfy two contracts at once. You stack bounds with a plus sign: T: Summary + Priced reads "T is both Summary and Priced", and the body may call methods from both traits. Run this:
// Two bounds with +: T must be BOTH Summary AND Priced. A where clause moves
// the same bounds below the signature when the line gets crowded.
trait Summary {
fn summary(&self) -> String;
}
trait Priced {
fn price(&self) -> u32;
}
struct Book {
title: String,
cents: u32,
}
impl Summary for Book {
fn summary(&self) -> String {
format!("the book \"{}\"", self.title)
}
}
impl Priced for Book {
fn price(&self) -> u32 {
self.cents
}
}
// Inline form: one short line.
fn label<T: Summary + Priced>(item: &T) -> String {
format!("{} costs {} cents", item.summary(), item.price())
}
// where form: identical meaning, easier to read with many or long bounds.
fn label_where<T>(item: &T) -> String
where
T: Summary + Priced,
{
format!("{} costs {} cents", item.summary(), item.price())
}
fn main() {
let b = Book { title: "Rust".to_string(), cents: 3999 };
println!("{}", label(&b));
println!("{}", label_where(&b));
}label requires T: Summary + Priced right in the angle brackets, so its body can call both item.summary() and item.price(). That inline form is fine for one or two short bounds, but it crowds the signature fast: with three generic parameters, each carrying two or three bounds, the line between the angle brackets and the parameter list becomes unreadable. The where clause is the fix. It moves every bound to its own block below the signature, as in label_where, leaving the parameter list clean. The two functions mean exactly the same thing; where is purely a matter of where you write the constraints, not what they are.
A bound never adds behavior to a type; it only requires behavior the type already has. If you call label(&x) and x's type implements Summary but not Priced, the code does not compile: the bound is unmet. The fix is never to weaken the bound the function genuinely needs; it is to implement the missing trait for the type, or to pass a type that already does.
Coherence and the orphan rule
Now a rule that surprises people coming from dynamic languages, because it forbids something they take for granted. In Ruby or Python you can reopen any class, even one from the standard library, and bolt new methods onto it. Rust will not let you do the unrestricted version of that with traits. The orphan rule says: you may write impl Trait for Type only if you own the trait, or you own the type, or both. You may not implement someone else's trait for someone else's type, because then the impl is an orphan, with no parent crate that clearly owns it.
Concretely: you can implement your own Summary trait for the standard library's String (you own the trait). You can implement the standard library's Display trait for your own Server (you own the type). But you cannot implement Display (not yours) for String (not yours): both belong to other crates, and the impl would be an orphan. When you need behavior across that line, the idiom is to wrap the foreign type in a thin struct of your own (the newtype pattern, III.06) and implement on the wrapper, which you do own.
The reason is coherence: the guarantee that for any given type and trait, the whole program has at most one implementation. Imagine two unrelated libraries you depend on each implementing Display for String in their own way. When your code calls some_string.to_string(), which impl runs? There is no good answer, and worse, adding a dependency could silently change the behavior of code that never mentioned it. The orphan rule makes that collision impossible to express, so a trait method always resolves to one unambiguous body.
Coherence means one type plus one trait equals at most one impl, program-wide, forever. The orphan rule enforces it cheaply: an impl is only legal in the crate that owns the trait or the type, so two crates can never both claim the same pairing. The cost is that you cannot graft a third party's trait onto a third party's type directly. The payoff is that adding a dependency can never silently rewire which method body your existing calls dispatch to.
Associated types, supertraits, and blanket impls
Three constructs round out what a trait can express. The first answers a design question: when a trait produces a value, who picks the value's type? Sometimes the implementer should pick it once and for all, not the caller. That is an associated type: a type the trait names with type Item; and each implementer fixes with type Item = SomeType; in its impl. Run this:
// An associated type is a single output type the implementer chooses once, named
// in the trait as `type Item`. Compare that to a generic trait parameter, which
// the CALLER could pick many times over. A producer that yields one kind of value
// wants exactly one Item, so an associated type is the honest fit.
trait Producer {
// The implementer names this once. Each type has exactly one Item.
type Item;
fn produce(&mut self) -> Self::Item;
}
struct Counter {
next: u32,
}
struct Letters {
next: u8,
}
impl Producer for Counter {
type Item = u32; // Counter produces u32, and only u32.
fn produce(&mut self) -> u32 {
let value = self.next;
self.next += 1;
value
}
}
impl Producer for Letters {
type Item = char; // Letters produces char.
fn produce(&mut self) -> char {
let value = self.next as char;
self.next += 1;
value
}
}
fn main() {
let mut c = Counter { next: 1 };
println!("{} {} {}", c.produce(), c.produce(), c.produce());
let mut l = Letters { next: b'a' };
println!("{} {} {}", l.produce(), l.produce(), l.produce());
// The std Iterator trait is built exactly this way, with type Item.
}Producer declares type Item;, and its produce method returns Self::Item, "whatever this implementer's Item is". Counter sets type Item = u32; Letters sets type Item = char. Each type has exactly one Item, chosen by the implementer. Contrast that with a generic trait parameter like trait Producer<Item>, which the caller could fill many different ways for the same type, so one type could be Producer<u32> and Producer<char> at once. When a type produces a single kind of value, the associated type captures that "exactly one" cleanly. This is precisely how the standard Iterator trait is built, with type Item, which is why you only ever say what kind of thing an iterator yields once.
The second construct is a supertrait: one trait requiring another as a precondition. The third is a blanket impl: implementing a trait for every type that already meets some bound, in a single stroke. Run this:
// A supertrait says "to be Loud, you must first be Named": Loud: Named. So a Loud
// method may call Named's methods, because every Loud type is guaranteed to be Named.
trait Named {
fn name(&self) -> String;
}
trait Loud: Named {
fn announce(&self) -> String {
// Allowed because the supertrait bound guarantees self.name() exists.
format!(">>> {} <<<", self.name().to_uppercase())
}
}
struct Town {
label: String,
}
impl Named for Town {
fn name(&self) -> String {
self.label.clone()
}
}
// Town is Named, so it is allowed to be Loud, and it takes the default announce.
impl Loud for Town {}
// A blanket impl: implement Tagged for EVERY type that is already Named, in one
// stroke. The compiler writes the impl for each qualifying type.
trait Tagged {
fn tag(&self) -> String;
}
impl<T: Named> Tagged for T {
fn tag(&self) -> String {
format!("[{}]", self.name())
}
}
fn main() {
let t = Town { label: "Ada".to_string() };
println!("{}", t.announce()); // from Loud's default
println!("{}", t.tag()); // from the blanket impl, because Town is Named
}trait Loud: Named is the supertrait syntax: the : Named says a type cannot be Loud unless it is also Named. That precondition is what lets Loud's default announce call self.name(): the supertrait bound guarantees every Loud type has a name. Read A: B on a trait as "A requires B", the same colon you read as a bound everywhere else.
The blanket impl is the block impl<T: Named> Tagged for T. It does not implement Tagged for one type; it implements it for every T that is already Named, all at once. The compiler writes the impl for each qualifying type, so Town gets a .tag() method without a single line of Tagged-specific code in Town's definition. Blanket impls are how the standard library gives, for example, every type that is Display a matching ToString: one impl, written once, covering a whole universe of types.
Supertrait sounds hierarchical, but it is only a bound on a trait: Loud: Named means "to be Loud you must be Named", nothing more. Blanket impl sounds exotic, but it is just a generic impl whose subject is a bounded T rather than a named type. Neither is a new mechanism; both are bounds you already understand, pointed at a trait or at an impl.
Deriving traits, and returning impl Trait
Some traits are so mechanical that writing them by hand is pure tedium: comparing two structs field by field for equality, cloning each field, formatting every field for debugging. Rust automates exactly these with #[derive(...)], an attribute above a type that asks the compiler to generate a standard, field-by-field impl for you. It is not magic; it is the same impl you would have typed, written by a macro so you do not have to. Run this:
// #[derive(...)] asks the compiler to write a trait impl for you, field by field,
// the same impl you would type by hand. Here four traits come for free.
#[derive(Debug, Clone, PartialEq, Default)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let a = Point { x: 1, y: 2 };
// Clone: a recursive, field-by-field copy.
let b = a.clone();
// PartialEq: gives you == and !=, comparing field by field.
println!("a == b: {}", a == b);
// Debug: the {:?} formatter, for println! and error messages.
println!("a is {a:?}");
// Default: Point::default(), each field set to ITS default (0 for i32).
let origin = Point::default();
println!("origin is {origin:?}");
// The derives compose: this would be a wall of hand-written impls otherwise.
println!("origin == a: {}", origin == a);
}The one line #[derive(Debug, Clone, PartialEq, Default)] gives Point four trait implementations. Debug enables the {:?} formatter you have used since Part I. Clone gives .clone(), a field-by-field copy. PartialEq gives == and !=, comparing field by field. Default gives Point::default(), building a value from each field's own default (0 for an i32). Each derive expands to an ordinary impl block; the attribute just spares you from typing the obvious one. You only fall back to a hand-written impl when the obvious, field-by-field behavior is not what you want.
Last, the mirror image of an argument bound: impl Trait in return position. A function can promise to return "some type that is Shape" without naming which one, by writing -> impl Shape. Run this:
// Returning impl Trait: the function promises "some single type that is Shape",
// without naming it. The caller gets a concrete value and may use Shape's methods,
// but does not get to know which type it is.
trait Shape {
fn area(&self) -> f64;
}
struct Circle {
r: f64,
}
struct Square {
side: f64,
}
impl Shape for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.r * self.r
}
}
impl Shape for Square {
fn area(&self) -> f64 {
self.side * self.side
}
}
// Returns one unnamed type that is Shape. Note: every path must return the SAME
// concrete type. Returning a Circle here and a Square there would not compile;
// that mixed case is what trait objects (III.11) are for.
fn unit_shape() -> impl Shape {
Circle { r: 1.0 }
}
fn squarish(side: f64) -> impl Shape {
Square { side }
}
fn main() {
let a = unit_shape();
let b = squarish(3.0);
println!("unit circle area: {:.4}", a.area());
println!("3x3 square area: {:.1}", b.area());
}unit_shape returns impl Shape, so the caller gets a real value it can call .area() on, but the function does not have to spell out (and the caller does not get to see) that the type is actually Circle. This is useful when the concrete type is long, private, or simply not worth naming, which is why iterator chains so often return impl Iterator. There is one firm rule: a single function returning impl Shape must return the same one concrete type on every path. You cannot return a Circle from one branch and a Square from another, because impl Trait is one hidden type, not a choice among several.
When you genuinely need to return different concrete types from different branches, or to hold a collection of mixed types that all share a trait, impl Trait cannot do it: it names one hidden type, the same on every path. The tool for a heterogeneous, "any of these, behind one trait" collection is the trait object, and that is the whole of the next chapter, III.11.
The whole contract
Step back and the chapter is one idea seen from several angles. A trait is a contract: a named set of method signatures. A type signs the contract with an impl Trait for Type block, optionally taking the trait's default method bodies and overriding the rest. A generic demands the contract with a bound, T: Trait, and in exchange may call the trait's methods on its T; stack bounds with + and tidy them with where. Coherence and the orphan rule keep every type-and-trait pairing resolving to exactly one implementation. And associated types, supertraits, blanket impls, derive, and impl Trait in return position are the refinements that make the contract precise and convenient.
A trait names what a type can do; an impl is a type promising to do it; a bound is a generic requiring that promise so it can call the methods. The compiler checks every promise and every requirement before the program runs, so "this T can do X" is a fact, not a hope.
Two threads run ahead from here, each with its own home. When you need a collection of different types behind one trait, decided at runtime, you reach for trait objects, which is Part III, chapter 11. And the special traits that wire up operators (+, ==) and value conversions (From, Into) get rigorous treatment in Part III, chapter 12. This chapter gave you the contract; those two cash it in.
The whole chapter in seven lines
- A trait is a named set of method signatures: a contract that a type can promise to fill.
- A type signs it with
impl Trait for Type, supplying a body for each required method and inheriting any default method bodies it does not override. - A trait bound,
T: Trait, lets a generic call the trait's methods on itsT;impl Traitin argument position is the unnamed shorthand. - Stack bounds with
+and move them below the signature with awhereclause; the meaning is unchanged. - The orphan rule (coherence) lets you
impla trait only if you own the trait or the type, so each pairing resolves to exactly one implementation. - Associated types let the implementer fix one output type, supertraits require another trait, and blanket impls implement a trait for every type meeting a bound.
#[derive]generates the obvious field-by-fieldimpl, and-> impl Traitreturns one unnamed type that is the trait.