Traits

Traits are Rust's answer to interfaces. They define behavior that types can share — without inheritance.

Defining and implementing a trait

#![allow(unused)]
fn main() {
trait Resettable {
    fn reset(&mut self);
}

impl Resettable for Cpu {
    fn reset(&mut self) {
        self.pc = 0x200;
        self.v = [0; 16];
    }
}

impl Resettable for Display {
    fn reset(&mut self) {
        self.framebuffer = [[false; 64]; 32];
    }
}
}

Any function that accepts T: Resettable works with both — no class hierarchy needed.

Default implementations

A trait can provide a default implementation that types use unless they override it:

#![allow(unused)]
fn main() {
trait Resettable {
    fn reset(&mut self);

    fn reset_and_log(&mut self) {    // default — types get this for free
        self.reset();
        println!("reset complete");
    }
}
}

This is similar to abstract classes in OOP, but without shared state.

Standard library traits you will encounter

TraitWhat it does
Defaultprovides a "zero" value, enables Cpu::default()
Debugenables {:?} formatting, essential for tests and logging
Cloneenables explicit .clone() to duplicate a value

Many can be derived automatically — the compiler generates the implementation from the struct definition:

#![allow(unused)]
fn main() {
#[derive(Debug, Clone)]
pub struct Cpu { ... }
}

The key difference from inheritance

Traits share behavior, not state. When Cpu and Display both implement Resettable, they share the contract but not any data. There is no base class with shared fields, no diamond problem, no fragile hierarchy.


In the emulator: We use #[derive(Debug)] on most structs so they can be printed in test output. The Default trait comes up when discussing Constructors. The trait system is also what makes external crates like rand work — we bring the Rng trait into scope to access its methods (see External Crates and RND opcode).