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

Newtype Pattern and Semantic Wrappers

Rule

Use newtypes for IDs, units, validated values, and public API meaning; do not wrap primitives when no safety, validation, or clarity is gained.

Why

Newtypes prevent argument swaps, attach validation to values, and give APIs stable domain names independent of primitive representation.

Do

  • Keep meaningful or validated inner fields private.
  • Use new for infallible wrappers and try_new for validated values.
  • Borrow textual input when validation or normalization derives the stored value.
  • Expose only focused views and consuming conversions.
  • Follow deriving policy for standard traits.
  • Follow conversion naming for as_* and into_* methods.
  • Use #[repr(transparent)] only for a documented layout requirement.

Avoid

  • Do not wrap every primitive.
  • Do not expose an invariant-bearing inner field publicly.
  • Do not implement Deref merely to inherit primitive methods.
  • Do not use From when conversion validates or can fail.
  • Do not choose vague names such as Value or Id outside a narrow domain module.

Public API Notes

Published APIs should use newtypes where primitive arguments can be confused or representation may evolve. Application internals need them mainly at boundaries and for real domain meaning.

Example

#![allow(unused)]
fn main() {
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct EmailAddress(String);

impl EmailAddress {
    pub fn try_new(value: &str) -> Result<Self, EmailAddressError> {
        let value = value.trim();
        if !value.contains('@') {
            return Err(EmailAddressError::MissingAt);
        }
        Ok(Self(value.to_owned()))
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}
}

Exceptions

  • Use a public tuple field for an intentionally transparent wrapper without invariants or evolution pressure.
  • Use Deref for a genuinely pointer-like abstraction.
  • Use a primitive when the value is local, obvious, and does not cross an API boundary.