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
| Trait | What it does |
|---|---|
Default | provides a "zero" value, enables Cpu::default() |
Debug | enables {:?} formatting, essential for tests and logging |
Clone | enables 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. TheDefaulttrait comes up when discussing Constructors. The trait system is also what makes external crates likerandwork — we bring theRngtrait into scope to access its methods (see External Crates and RND opcode).