---
title: 'Trait Definition and Implementation'
source: 'https://academia.sh/en/courses/rust-abstraction/trait-definition-and-implementation'
course: 'Traits, Generics and Error Handling'
language: en
updated: '2026-09-15T04:58:43+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Shows that a trait definition establishes a behavior contract and can carry a default method body'
    - 'Verifies by running the measurement kernel that a default body runs every time for one type and never for a type that overrides it'
    - 'Distinguishes the trait concept from the interface concept and explains the difference in measurement axis with Interfaces, Errors and Generics'
---

# Trait Definition and Implementation

A trait establishes a behavior contract independent of type and can carry a default method body; when a sixteen-body measurement kernel runs, this default body is seen to run four times for one type and zero times for a type that overrides it — the question asked throughout the course is this: how many times did this body you wrote in the source run?

The Ownership and the Memory Model course measured where a value's life ends: at the end of
its scope, where it was moved, in an explicit call, or together with its container. While a
value stays in hand for the length of its life, **what is done with it** is a separate
question, and this course looks there. Separating a behavior from a type — introducing the
same function under the same name to more than one type — turns into a decision that also
determines **how many times** that behavior's body runs. One question is asked throughout the
course: how many times did this body you wrote in the source run?

There are three answers to this question, and these three names do not change throughout the
course.

## Three Classes

| Class | Definition |
|---|---|
| every time | Runs unavoidably, per call or per element |
| only when needed | Runs only when the result is requested, as much as needed |
| never | Written in the source, but did not run even once in this run |

This trio should not be confused with the two questions already asked earlier. The
Object-Oriented Java course measured *which type made the decision* — the declared type or
the runtime type. The Interfaces, Errors and Generics course (Go) measured *where the binding
was established* — at declaration, at the call site, or inside the value. This course asks
neither; it asks **counting**. By the time a trait implementation is written, the binding is
already established — what's measured in this lesson is how many times that body was
triggered at run time, after the binding was established.

A trait is a behavior contract: it declares one or more method signatures and lets those
methods be called under a type-independent name. The function definition in the Programming
Fundamentals course was bound to a single type; a trait generalizes this — the same method
name can correspond to a separate body for every type that implements it. This looks close to
the interface concept, but it is not the same thing: an interface was defined in the Go course
on the axis of *where* the binding is established (implicit implementation, without requiring
a declaration); a trait, in Rust, is a contract that a type declares **explicitly** with the
`impl Name for Type` syntax, and it can also carry a default body. The word trait will be used
untranslated throughout the course.

## Required and Optional Methods

A trait declaration lists a series of method signatures inside the body of `trait Name { ... }`.
Each signature can take one of two forms: only a header (`fn name(&self) -> Type;`, ending in
a semicolon, without a body) or a header plus a body (`fn name(&self) -> Type { ... }`). The
first form is **required**: every type that implements the trait must write this method in
its own `impl` block, otherwise the implementation is considered incomplete and the source
does not compile. The second form is **optional**: an implementing type may never write this
method — if it does not, the trait's own body takes over; if it does, its own body runs and the
trait's is never seen.

The `Label` trait used throughout this lesson shows all three at once. `ledger` and `kind` are
declared without a body — every implementation has to write these two itself, because the
trait itself cannot assume anything about a type's ledger or name. `name`, on the other hand,
is declared with a default body: `Plain` never writes this method and inherits the trait's
body, while `Custom` writes its own body and overrides the default. Required and optional
methods can coexist inside the same trait declaration; this is similar to a contract saying
that some of its clauses must differ for every signer (address, name) while others can start
with a shared default and be changed only when needed.

The `impl Label for Plain` syntax itself splits into two parts as well: `Label` says which
contract is being implemented; `Plain` says who implements it. The same trait can be
implemented repeatedly for different types with separate `impl` blocks — `times.rs` has two
separate `impl Label for ...` blocks for `Plain` and `Custom`, both carrying the same trait
name but implementing independent bodies.

## The Measurement Kernel

The kernel from which the rest of the course will take slices is `times.rs`. It classifies
sixteen separate bodies in a single run and records each one to an observation ledger. The
ledger reuses the mechanism built in the previous course's shared-ownership lesson:
`Rc<RefCell<BTreeMap<String, u32>>>` — a counter with more than one owner, carrying interior
mutability, and sorted.

```rust
// times.rs — how many times did a body run: every time, only when needed, or never.

use std::cell::RefCell;
use std::collections::BTreeMap;
use std::ops::Add;
use std::rc::Rc;

type Ledger = Rc<RefCell<BTreeMap<String, u32>>>;

fn record(ledger: &Ledger, name: &str) {
    *ledger.borrow_mut().entry(name.to_string()).or_insert(0) += 1;
}

fn times(ledger: &Ledger, name: &str) -> u32 {
    *ledger.borrow().get(name).unwrap_or(&0)
}

// --- trait: default body and the type that overrides it ---

trait Label {
    fn ledger(&self) -> &Ledger;
    fn kind(&self) -> &'static str;
    fn name(&self) -> String {
        let key = format!("default-body-{}", self.kind());
        record(self.ledger(), &key);
        String::from("default")
    }
}

struct Plain {
    ledger: Ledger,
}
struct Custom {
    ledger: Ledger,
}

impl Label for Plain {
    fn ledger(&self) -> &Ledger {
        &self.ledger
    }
    fn kind(&self) -> &'static str {
        "plain"
    }
}

impl Label for Custom {
    fn ledger(&self) -> &Ledger {
        &self.ledger
    }
    fn kind(&self) -> &'static str {
        "custom"
    }
    fn name(&self) -> String {
        record(self.ledger(), "overridden-body");
        String::from("custom")
    }
}

// --- generics vs. trait object: the same job, two call forms ---

fn static_dispatch<T: Label>(x: &T) -> String {
    record(x.ledger(), "static-dispatch");
    x.name()
}

fn dynamic_dispatch(x: &dyn Label) -> String {
    record(x.ledger(), "dynamic-dispatch");
    x.name()
}

// --- operator overloading ---

#[derive(Clone)]
struct Length {
    value: i32,
    ledger: Ledger,
}

impl Add for Length {
    type Output = Length;
    fn add(self, other: Length) -> Length {
        record(&self.ledger, "operator-body");
        Length {
            value: self.value + other.value,
            ledger: Rc::clone(&self.ledger),
        }
    }
}

// --- the error path ---

#[derive(Debug)]
struct NumberError;

impl From<std::num::ParseIntError> for NumberError {
    fn from(_: std::num::ParseIntError) -> NumberError {
        NumberError
    }
}

fn parse_doubled(ledger: &Ledger, text: &str, label: &str) -> Result<i32, NumberError> {
    let n: i32 = text.parse()?;
    record(ledger, label);
    Ok(n * 2)
}

fn eager_default(ledger: &Ledger) -> i32 {
    record(ledger, "eager-default");
    -1
}

fn main() {
    let ledger: Ledger = Rc::new(RefCell::new(BTreeMap::new()));
    let mut classified: Vec<(&str, &str)> = Vec::new();

    // 1-2. default body: runs for the type that does not override it, never for the one that does
    let plain = Plain {
        ledger: Rc::clone(&ledger),
    };
    let custom = Custom {
        ledger: Rc::clone(&ledger),
    };
    let _ = plain.name();
    let _ = custom.name();
    let _ = custom.name();

    // 3-4. the same job in two call forms
    let _ = static_dispatch(&plain);
    let _ = static_dispatch(&plain);
    let _ = dynamic_dispatch(&plain as &dyn Label);
    let _ = dynamic_dispatch(&custom as &dyn Label);

    // 5. operator body
    let a = Length {
        value: 3,
        ledger: Rc::clone(&ledger),
    };
    let b = Length {
        value: 4,
        ledger: Rc::clone(&ledger),
    };
    let sum = a.clone() + b;
    let sum = sum + a;
    let _ = sum.value;

    // 6. eager default: runs even when a value IS present
    let full: Option<i32> = Some(7);
    let _ = full.unwrap_or(eager_default(&ledger));

    // 7-8. lazy default: never when a value is present, once when it is absent
    let empty: Option<i32> = None;
    let _ = full.unwrap_or_else(|| {
        record(&ledger, "lazy-default-unneeded");
        -1
    });
    let _ = empty.unwrap_or_else(|| {
        record(&ledger, "lazy-default-needed");
        -1
    });

    // 9-10. after the question mark operator
    let _ = parse_doubled(&ledger, "21", "post-question-succeeded");
    let _ = parse_doubled(&ledger, "twenty-one", "post-question-failed");

    // 11. error conversion
    if parse_doubled(&ledger, "twenty-one", "ignored").is_err() {
        record(&ledger, "error-conversion");
    }

    // 12. unconsumed chain
    let _lazy = vec![1, 2, 3, 4].into_iter().map(|x| {
        record(&ledger, "unconsumed-chain");
        x * 2
    });

    // 13. consumed chain: on every element
    let sum: i32 = vec![1, 2, 3, 4]
        .into_iter()
        .map(|x| {
            record(&ledger, "consumed-chain");
            x * 2
        })
        .sum();
    let _ = sum;

    // 14. short-circuiting chain: only as much as needed
    let found = vec![1, 2, 3, 4]
        .into_iter()
        .map(|x| {
            record(&ledger, "short-circuiting-chain");
            x * 2
        })
        .find(|x| *x > 2);
    let _ = found;

    // 15. uncalled closure
    let _closure = || record(&ledger, "uncalled-closure");

    // --- report ---
    let bodies = [
        ("default-body-plain", "every time"),
        ("overridden-body", "every time"),
        ("static-dispatch", "every time"),
        ("dynamic-dispatch", "every time"),
        ("operator-body", "every time"),
        ("eager-default", "every time"),
        ("consumed-chain", "every time"),
        ("error-conversion", "every time"),
        ("lazy-default-needed", "only when needed"),
        ("post-question-succeeded", "only when needed"),
        ("short-circuiting-chain", "only when needed"),
        ("default-body-custom", "never"),
        ("lazy-default-unneeded", "never"),
        ("post-question-failed", "never"),
        ("unconsumed-chain", "never"),
        ("uncalled-closure", "never"),
    ];

    println!("{:<28}{:<22}{}", "body", "class", "how many times");
    for (name, class) in bodies {
        let n = times(&ledger, name);
        println!("{:<28}{:<22}{}", name, class, n);
        classified.push((name, class));
    }

    println!();
    for s in ["every time", "only when needed", "never"] {
        let count = classified.iter().filter(|(_, c)| *c == s).count();
        println!("{:<22}{}", s, count);
    }
    println!("total bodies: {}", classified.len());
}
```

```
body                        class                 how many times
default-body-plain          every time            4
overridden-body             every time            3
static-dispatch             every time            2
dynamic-dispatch            every time            2
operator-body               every time            2
eager-default               every time            1
consumed-chain              every time            4
error-conversion            every time            1
lazy-default-needed         only when needed      1
post-question-succeeded     only when needed      1
short-circuiting-chain      only when needed      2
default-body-custom         never                 0
lazy-default-unneeded       never                 0
post-question-failed        never                 0
unconsumed-chain            never                 0
uncalled-closure            never                 0

every time            8
only when needed      3
never                 5
total bodies: 16
```

**TG1.** This run is deterministic: it was verified across five separate runs that the
source's same sixteen lines are produced in the same order with the same class distribution.
**8 of the sixteen bodies ran every time, 3 ran only when needed, 5 never ran.** This number
does not change through the rest of the course; each lesson only takes its own slice. The
report uses a sorted map (`BTreeMap`) — because a hash map's iteration order could change from
run to run, the count here relies on a structure that guarantees ordering even for this.

## A Default Body Gives Two Different Results for Two Types

The `Label` trait declares three methods: `ledger` and `kind` carry no body at all — every
implementation has to write these itself. The third method, `name`, carries a **default
body**: since `impl Label for Plain` never touches this body, the trait's own body runs when
`name` is called on `Plain`. `Custom`, on the other hand, **overrides** `name` with its own
body — the default body in the trait never takes over for `Custom`.

The two rows in the table show this pair directly. `default-body-plain` ran four times:
`plain.name()` was called directly once, `static_dispatch(&plain)` was called twice and each
call triggered `x.name()` inside itself, and `dynamic_dispatch(&plain ...)` was called once
and it triggered `x.name()` the same way — four calls in total, all four landing on the
trait's default body. `overridden-body` ran three times: `custom.name()` twice, directly, and
`dynamic_dispatch(&custom ...)` once — all three ran `Custom`'s own body, never touching the
trait's default. Both belong to the **every time** class: they run unavoidably wherever
they are called, the only difference being which body runs.

**TG2.** The Interfaces, Errors and Generics course measured **where the binding was
established** for an implementation — in Go, a type implicitly implemented an interface
without any declaration. In Rust this binding is established **explicitly** with the
`impl Label for Plain` line; this course notes that difference once and moves on. What's
actually measured here is how many times that implementation's body is triggered at run time
**after** the binding is established — the difference of 4 vs. 3 between `default-body-plain`
and `overridden-body` comes from the number of calls, not from the binding itself.

`eager-default`, `operator-body`, and `error-conversion` on the error path fall into the same
class too — they run every time, because each one in the kernel runs exactly as many times as
it is called. The course's later lessons will each grow one of these rows in turn: Trait
Bounds will grow whether `static-dispatch` depends on a bound, Associated Types will grow the
`Output` field in `operator-body`'s signature, Object Safety and Dynamic Dispatch will put
`static-dispatch` and `dynamic-dispatch` side by side.

## The Measurement's Limit

The `default-body-custom` row is zero. This does not mean the default body **does not exist** —
it sits in the source, inside the `trait Label` declaration, exactly as it did when it ran for
`Plain`. The reason it is zero is that `Custom` writes its own `name` method and never touches
this body. The same source line runs four times for one type while never running for another;
the run count does not measure whether the body exists, it measures which types **use** it.

This distinction will come up again through the rest of the course: none of the five bodies in
the `never` class have been deleted from the source — all five were not triggered even
once in this run. A count of zero does not mean "this code is unnecessary"; it means "this path
was not chosen in this run."

The default body itself was also written to make this visible: its key string is built by
placing the value returned by the `self.kind()` call into a pattern, not a fixed string. The
trait's own body never knows the name of the type running it — `self.kind()` produces a
string, and the body borrows it and appends it to the key. This lets a single default body be
counted in a separate row for every type that inherits it; even if `Plain` and `Custom` shared
the same default body (in this example they do not, because `Custom` wrote its own), they could
still be counted separately under two separate keys. This small design decision also explains
why the kernel can be sliced in the rest of the lessons: every body carries its own key, so
taking one slice does not affect the remaining keys.

## Summary

- A trait establishes a type-independent behavior contract and can carry a default body that
  takes over when the implementing type does not write one.
- The measurement kernel classifies sixteen bodies: 8 ran every time, 3 only when needed, 5
  never — this number does not change through the course.
- The same default body runs four times (every time) for one type while running zero times
  (never) for a type that overrides it — the body in the source is the same, the run count
  differs.
- trait and interface are separate concepts: interface was defined in Go on the axis of where
  the binding is established, trait here is a contract established with an explicit
  declaration that can carry a default body.
- A run count of zero does not mean the body is missing from the source; it means that path was
  never taken for that type.

## Next Step

The body of a type implementing a trait could be measured, but to use a body inside a generic
function, that function's type parameter needs a **bound**. The next lesson asks whether this
bound touches the run count at all — the same body is written and run in both a bounded and an
unbounded form.
