Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Struct Design and Encapsulation

Rule

Model meaningful concepts as structs with private fields and behavior-bearing methods; use public fields only for plain data without invariants.

Why

Private fields let a type control construction and mutation, while methods make ownership and behavior explicit.

Do

  • Keep invariant-bearing fields private.
  • Put behavior on the type that owns the required data.
  • Use &self for observation, &mut self for mutation, and self for consuming transitions.
  • Expose only the read accessors callers need.
  • Use pub(crate) only for real internal module boundaries.
  • Use public fields for DTOs, configuration records, snapshots, and other intentional plain data.

Avoid

  • Do not make fields public to avoid constructors or accessors.
  • Do not put methods on wrappers that do not own the relevant data or invariant.
  • Do not generate getters and setters for every field.
  • Do not expose production mutation hooks solely for tests.
  • Do not split natural behavior into unrelated helper modules.

Public API Notes

Public fields prevent constructors and destructuring from evolving independently. Published invariant-bearing types therefore keep fields private.

Example

#![allow(unused)]
fn main() {
pub struct UserAccount {
    email: EmailAddress,
    active: bool,
}

impl UserAccount {
    pub fn email(&self) -> &EmailAddress {
        &self.email
    }

    pub fn deactivate(&mut self) {
        self.active = false;
    }
}

pub struct UserSummary {
    pub email: EmailAddress,
    pub active: bool,
}
}

Exceptions

  • Use tuple structs for small semantic wrappers as described by newtypes.
  • Use free functions for algorithms with no natural owner type.