tomevault-io

domain-newtypes-over-primitives

Use when introducing API parameters, struct fields, or return types that carry a domain value — represent them with a domain newtype that enforces their invariants.

tomevault-io 1 Updated 1mo ago
GitHub

Install

npx skillscat add tomevault-io/tomes/domain-newtypes-over-primitives

Install via the SkillsCat registry.

SKILL.md

Use Domain Newtypes, Not Raw Primitives

Rule

When an API boundary takes or returns a value with a domain meaning, define a newtype that:

  • Validates the value at construction time (see validate-in-constructor).
  • Exposes the inner representation only through deliberate accessors.
  • Is used at every API boundary touching the concept (not raw u64/Word/tuples).

Raw (AccountId, u64) tuples, bare Word parameters, and primitive-typed amounts must be replaced with a named type like FungibleAsset, FaucetId, BlockNumber.

Why

A newtype is the one place an invariant gets enforced; once a function takes a raw u64 for an amount, every caller and reviewer must re-check the bound. It also localizes representation changes to a single type instead of every signature.

Examples

// Good
pub fn mint(asset: FungibleAsset, to: AccountId) -> Result<Receipt, Error>;

// Bad
pub fn mint(faucet_id: AccountId, amount: u64, to: AccountId) -> Result<Receipt, Error>;
// Good: validated wrapper with explicit constructor
pub struct BlockNumber(u32);
impl BlockNumber {
    pub fn new(n: u32) -> Result<Self, Error> {
        if n > MAX_BLOCK_NUMBER { return Err(Error::OutOfRange); }
        Ok(Self(n))
    }
}

// Bad: raw u32 leaks into every signature, every caller checks the bound
pub fn lookup_block(n: u32) -> Option<Block>;

Source: 0xMiden/protocol — distributed by TomeVault.