House Style and Rust Philosophy
Rule
Write idiomatic Rust with an OO-leaning default: model domain concepts as structs with methods, protect invariants, and compose behavior explicitly.
Why
Rust supports data with behavior without inheritance. Clear ownership and composition provide structure without forcing object-oriented patterns that do not fit the language.
Do
- Start with domain types when values carry distinct meaning.
- Put behavior on the type that owns the relevant data or invariant.
- Prefer direct composition with explicit fields and methods.
- Use free functions for algorithms or cross-type operations with no natural receiver.
- Use functional pipelines when they are clearer than stateful updates.
- Follow the owner pages for fields, traits, and iteration.
Avoid
- Do not emulate inheritance hierarchies with traits, enums, or nested wrappers.
- Do not move natural type behavior into stateless helper modules.
- Do not expose helpers publicly only to make private behavior testable.
- Do not create pass-through wrappers or delegation machinery around a confused boundary.
- Do not choose a design-pattern name over simpler Rust types, modules, and ownership.
Example
#![allow(unused)]
fn main() {
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Money {
cents: u64,
}
impl Money {
pub const ZERO: Self = Self { cents: 0 };
pub fn checked_add(self, other: Self) -> Option<Self> {
self.cents
.checked_add(other.cents)
.map(|cents| Self { cents })
}
}
}
Exceptions
- Use plain data structs when their fields are the complete API and carry no invariants.
- Introduce a trait before a second implementation only when callers already need substitution or a testing seam.