# Traits & generics

> Define shared behaviour with traits, write generic code with bounds, and choose between impl Trait, generics and dyn Trait.

Rust roadmap · Core language · 19 min read · https://levelcode.org/roadmaps/rust/traits-and-generics

Traits are Rust's way of describing what a type can do, and generics let you write code once for every type that can do it. Together they replace interfaces, abstract classes and templates from other languages, with one important twist: by default, generic code is compiled separately for each concrete type, so abstraction costs nothing at runtime. This lesson covers defining and implementing traits, generic functions and structs, trait bounds, the standard traits you'll derive and implement constantly, and when to use dynamic dispatch with `dyn Trait` instead.

## Defining and implementing traits

A trait declares methods a type must provide. It can also provide default implementations:

```rust
trait Summary {
    fn author(&self) -> String;

    fn summarize(&self) -> String {
        format!("(Read more from {}...)", self.author()) // default, uses the required method
    }
}

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

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

impl Summary for Post {
    fn author(&self) -> String {
        self.author.clone()
    }
    fn summarize(&self) -> String {
        format!("{}, by {}", self.title, self.author) // overrides the default
    }
}

impl Summary for Tweet {
    fn author(&self) -> String {
        format!("@{}", self.username)
    }
    // uses the default summarize
}

fn main() {
    let p = Post { title: "Ownership explained".into(), author: "Ana".into() };
    let t = Tweet { username: "ferris".into(), text: "hi".into() };
    println!("{}\n{} ({} chars)", p.summarize(), t.summarize(), t.text.len());
}
```

Unlike Go's [implicit interfaces](https://levelcode.org/concepts/go-interfaces), implementation is explicit: you write `impl Summary for Post`. That makes it searchable, and it means a type can't satisfy a trait by accident just because a method name matches.

You can implement your own traits for any type, including standard ones like `i32` or `Vec<T>`, and standard traits for your own types. What you can't do is implement a foreign trait for a foreign type (say, `Display` for `Vec<u8>`). This **orphan rule** guarantees that two crates can never provide conflicting implementations. The workaround is a newtype: `struct Hex(Vec<u8>);` and `impl Display for Hex`.

## Generic functions and trait bounds

A generic function takes type parameters in angle brackets. **Trait bounds** say what those types must be able to do:

```rust
fn largest<T: PartialOrd>(items: &[T]) -> Option<&T> {
    let mut best = items.first()?;
    for item in items {
        if item > best {
            best = item;
        }
    }
    Some(best)
}

fn main() {
    println!("{:?}", largest(&[3, 9, 2]));          // Some(9)
    println!("{:?}", largest(&["kiwi", "apple"]));  // Some("kiwi")
    println!("{:?}", largest::<f64>(&[]));          // None
}
```

Without `T: PartialOrd`, the compiler rejects `item > best`: it type-checks the generic body once, against the bounds, not against each caller's type. That's why generic errors in Rust show up where you write the function, not deep inside a caller's instantiation as with C++ templates.

Bounds get long, so there are three ways to write them:

```rust
use std::fmt::{Debug, Display};

// 1. Inline
fn show_a<T: Display + Clone>(x: T) -> String { format!("{x}") }

// 2. where clause: easier to read with several parameters
fn show_b<T, U>(t: T, u: U) -> String
where
    T: Display + Clone,
    U: Debug,
{
    format!("{t} {u:?}")
}

// 3. impl Trait in argument position: shorthand for an anonymous generic
fn show_c(x: impl Display) -> String { format!("{x}") }
```

### Monomorphization

When you call `largest` with `&[i32]` and `&[&str]`, the compiler generates two copies of the function, each specialized for its type. This is **monomorphization**. The result is as fast as hand-written code for each type, with full inlining. The cost is compile time and binary size, which grow with the number of distinct instantiations.

## Generic structs and impl blocks

Structs and enums can be generic too; you've already used `Vec<T>`, `Option<T>` and `Result<T, E>`.

```rust
#[derive(Debug)]
struct Pair<T> {
    a: T,
    b: T,
}

impl<T> Pair<T> {
    fn new(a: T, b: T) -> Self {
        Self { a, b }
    }
}

// Methods that only exist when T supports comparison and printing.
impl<T: PartialOrd + std::fmt::Display> Pair<T> {
    fn print_max(&self) {
        let max = if self.a >= self.b { &self.a } else { &self.b };
        println!("max is {max}");
    }
}

fn main() {
    Pair::new(3, 8).print_max();
    let p = Pair::new(vec![1], vec![2]); // fine: new has no bounds
    println!("{p:?}");                    // but p.print_max() wouldn't compile: Vec isn't Display
}
```

You can also implement a trait for every type that satisfies a bound. This is a **blanket implementation**, and the standard library uses it heavily: `impl<T: Display> ToString for T` is why every displayable type has `.to_string()`.

## Associated types

Some traits have a type that belongs to the implementation rather than being chosen by the caller. `Iterator` is the classic case:

```rust
struct Countdown(u32);

impl Iterator for Countdown {
    type Item = u32; // each implementation picks exactly one Item type

    fn next(&mut self) -> Option<Self::Item> {
        if self.0 == 0 {
            None
        } else {
            self.0 -= 1;
            Some(self.0 + 1)
        }
    }
}

fn main() {
    let v: Vec<u32> = Countdown(3).collect();
    println!("{v:?}"); // [3, 2, 1]
}
```

Implementing one method, `next`, gives `Countdown` every iterator adapter for free: `map`, `filter`, `sum`, `collect` and the rest come from default methods on the trait.

Use an associated type when there's one natural choice per implementing type (an iterator yields one kind of item). Use a generic parameter (`trait From<T>`) when a type can implement the trait many times for different `T`s.

## The standard traits

You'll derive or implement these constantly:

| Trait | Gives you | Usually |
| --- | --- | --- |
| `Debug` | `{:?}` formatting | `#[derive]` on nearly everything |
| `Clone` / `Copy` | `.clone()` / implicit copies | Derive; `Copy` only for small plain data |
| `PartialEq` / `Eq` | `==` and `!=` | Derive; `Eq` when equality is total (not floats) |
| `PartialOrd` / `Ord` | `<`, `>`, sorting | Derive, or implement for custom orderings |
| `Hash` | Use as a `HashMap` key | Derive together with `Eq` |
| `Default` | `T::default()`, `..Default::default()` | Derive, or implement for meaningful defaults |
| `Display` | `{}` formatting, `.to_string()` | Implement by hand for user-facing text |
| `From` / `Into` | Conversions; `?` uses `From` | Implement `From`; `Into` comes free |
| `Drop` | Cleanup on scope end | Implement for resource owners |

```rust
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
struct Version {
    major: u32,
    minor: u32,
    patch: u32,
}

impl std::fmt::Display for Version {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "v{}.{}.{}", self.major, self.minor, self.patch)
    }
}

fn main() {
    let mut vs = vec![
        Version { major: 1, minor: 10, patch: 0 },
        Version { major: 1, minor: 2, patch: 7 },
        Version::default(),
    ];
    vs.sort(); // derived Ord compares fields in declaration order
    let names: Vec<String> = vs.iter().map(|v| v.to_string()).collect();
    println!("{names:?}"); // ["v0.0.0", "v1.2.7", "v1.10.0"]
}
```

> [!TIP]
> Implement `From<A> for B`, never `Into<B> for A`. A blanket impl in the standard library gives you `Into` automatically. Accepting `impl Into<String>` in a constructor lets callers pass `&str` or `String` without calling `.to_string()` themselves.

## Dynamic dispatch with dyn Trait

Generics need every type known at compile time. Sometimes you need a collection of *different* types that share a trait, or you want to choose an implementation at runtime. That's a **trait object**, `dyn Trait`, always behind a pointer such as `Box<dyn Trait>` or `&dyn Trait`:

```rust
trait Shape {
    fn area(&self) -> f64;
    fn name(&self) -> &str;
}

struct Circle(f64);
struct Square(f64);

impl Shape for Circle {
    fn area(&self) -> f64 { std::f64::consts::PI * self.0 * self.0 }
    fn name(&self) -> &str { "circle" }
}

impl Shape for Square {
    fn area(&self) -> f64 { self.0 * self.0 }
    fn name(&self) -> &str { "square" }
}

fn main() {
    let shapes: Vec<Box<dyn Shape>> = vec![Box::new(Circle(1.0)), Box::new(Square(2.0))];
    for s in &shapes {
        println!("{} {:.2}", s.name(), s.area());
    }
    let total: f64 = shapes.iter().map(|s| s.area()).sum();
    println!("{total:.2}");
}
```

A `Box<dyn Shape>` is a fat pointer: a pointer to the data plus a pointer to a **vtable** of the concrete type's methods. Each call looks up the method in the vtable at runtime. That's what Java, C# and Go interfaces do for every call.

| | Generics (`T: Trait`, `impl Trait`) | Trait objects (`dyn Trait`) |
| --- | --- | --- |
| Dispatch | Static: resolved at compile time, inlinable | Dynamic: vtable lookup per call |
| Mixed types in one collection | No | Yes |
| Binary size | One copy per type used | One copy |
| Runtime cost | None | An indirect call; blocks inlining |
| Restrictions | None | Trait must be *dyn compatible* (no generic methods, no `Self` returns) |

Default to generics. Reach for `dyn Trait` when you need heterogeneous collections, plugin-style registries, or to keep compile times and binary size down for code that isn't hot. The [static vs dynamic dispatch](https://levelcode.org/concepts/static-vs-dynamic-dispatch) concept covers the trade-off in more depth.

### impl Trait in return position

`-> impl Trait` returns some concrete type the caller doesn't get to name. It's still static dispatch, and it's how you return closures and iterator chains whose real types are unspeakable:

```rust
fn evens(limit: u32) -> impl Iterator<Item = u32> {
    (0..limit).filter(|n| n % 2 == 0)
}

fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
    move |x| x + n
}

fn main() {
    println!("{:?}", evens(10).collect::<Vec<_>>());
    println!("{}", make_adder(5)(10));
}
```

The function must return a single concrete type on every path. If different branches return different types, you need `Box<dyn Trait>`.

## Practice

Build a tiny notification system.

1. Define `trait Notifier { fn send(&self, to: &str, msg: &str) -> Result<(), String>; fn name(&self) -> &'static str { "notifier" } }`.
2. Implement it for `struct Email { from: String }` and `struct Sms { sender_id: u32 }`, overriding `name`.
3. Write a **generic** function `fn notify_all<N: Notifier>(n: &N, users: &[&str], msg: &str) -> usize` that returns how many sends succeeded. Make `Sms` fail for any number that doesn't start with `+`.
4. Write a `struct Dispatcher { channels: Vec<Box<dyn Notifier>> }` with `fn broadcast(&self, to: &str, msg: &str)` that tries every channel and prints the result for each.
5. Explain why `notify_all` can't be used to store both notifiers in one `Vec`, and why `Dispatcher` can.

**Solution**

```rust
trait Notifier {
    fn send(&self, to: &str, msg: &str) -> Result<(), String>;
    fn name(&self) -> &'static str {
        "notifier"
    }
}

struct Email {
    from: String,
}

struct Sms {
    sender_id: u32,
}

impl Notifier for Email {
    fn send(&self, to: &str, msg: &str) -> Result<(), String> {
        println!("email {} -> {to}: {msg}", self.from);
        Ok(())
    }
    fn name(&self) -> &'static str {
        "email"
    }
}

impl Notifier for Sms {
    fn send(&self, to: &str, msg: &str) -> Result<(), String> {
        if !to.starts_with('+') {
            return Err(format!("{to} is not a phone number"));
        }
        println!("sms #{} -> {to}: {msg}", self.sender_id);
        Ok(())
    }
    fn name(&self) -> &'static str {
        "sms"
    }
}

fn notify_all<N: Notifier>(n: &N, users: &[&str], msg: &str) -> usize {
    users.iter().filter(|u| n.send(u, msg).is_ok()).count()
}

struct Dispatcher {
    channels: Vec<Box<dyn Notifier>>,
}

impl Dispatcher {
    fn broadcast(&self, to: &str, msg: &str) {
        for ch in &self.channels {
            match ch.send(to, msg) {
                Ok(()) => println!("[{}] sent", ch.name()),
                Err(e) => println!("[{}] failed: {e}", ch.name()),
            }
        }
    }
}

fn main() {
    let sms = Sms { sender_id: 7 };
    println!("{}", notify_all(&sms, &["+4512345678", "ana@example.com"], "hi")); // 1

    let d = Dispatcher {
        channels: vec![Box::new(Email { from: "ops@example.com".into() }), Box::new(sms)],
    };
    d.broadcast("+4512345678", "deploy finished");
}
```

`notify_all::<Sms>` and `notify_all::<Email>` are two separate monomorphized functions; each call works with exactly one concrete type, so it can't hold a mix. `Vec<Box<dyn Notifier>>` erases the concrete type behind a vtable pointer, so each element can be a different type and calls are dispatched at runtime.

## Key takeaways

- A trait lists methods a type must implement, optionally with defaults; implementations are explicit, and the orphan rule prevents conflicts between crates.
- Generic code is checked against its bounds once and monomorphized per type: zero runtime cost, more compile time and binary size.
- Use associated types when each implementation has one natural choice (like `Iterator::Item`).
- Derive the standard traits (`Debug`, `Clone`, `PartialEq`, `Hash`, `Default`…) and implement `Display` and `From` by hand.
- Prefer generics and `impl Trait`; use `Box<dyn Trait>` for heterogeneous collections and runtime choice, at the cost of a vtable call.

## Quiz

1. Why does fn max<T>(a: T, b: T) -> T { if a > b { a } else { b } } fail to compile?

   A. Generic functions can't return T
   B. T has no PartialOrd bound, so > isn't known to exist for it
   C. a and b must be references
   D. Generic functions need a where clause

   **Answer: B.** Rust checks generic bodies against the declared bounds, not against callers' types. Adding T: PartialOrd makes > available.

2. You need a Vec holding both FileLogger and HttpLogger values that implement Logger. Which type works?

   A. Vec<impl Logger>
   B. Vec<T> where T: Logger
   C. Vec<Box<dyn Logger>>
   D. Vec<Logger>

   **Answer: C.** A Vec stores one element type. Box<dyn Logger> erases the concrete type behind a pointer and vtable. impl Logger and a generic T both stand for a single concrete type.

3. What does monomorphization mean for a generic function called with three different types?

   A. The compiler generates three specialized copies, each statically dispatched
   B. One copy that checks the type at runtime
   C. The function is boxed automatically
   D. The program fails to compile unless the types share a trait object

   **Answer: A.** Each concrete instantiation gets its own compiled copy, so calls are direct and inlinable. The trade-off is larger binaries and longer builds.

4. Why can't you write impl std::fmt::Display for Vec<u8> in your crate?

   A. Vec<u8> already implements Display
   B. Display can only be derived
   C. Display is dyn-incompatible
   D. The orphan rule: both the trait and the type are defined in other crates

   **Answer: D.** You may implement a trait for a type only if the trait or the type is local to your crate. Wrap it in a newtype, e.g. struct Hex(Vec<u8>), and implement Display for that.
