Rust Types and APIs
Use this when the real question is how much of the design should be expressed
in types, traits, and public signatures.
Working stance
- Make invalid states hard to represent before adding runtime checks.
- Keep public APIs narrower than internal helper code.
- Prefer concrete types until abstraction pressure is real.
- Use trait objects for extension seams and heterogeneity, not by reflex.
- Prefer typed domains and borrowed read-only inputs over stringly or
collection-specific signatures.
- Accept broad inputs; return precise outputs.
Decision surface
- Generics: the caller should stay generic and one concrete type flows through
each call site.
dyn Trait: runtime heterogeneity, plugin seams, or monomorphization costs
matter more than static dispatch.
- Newtype: domain distinction matters and misuse should not compile.
- Typestate: operation order is finite and important enough to encode.
- Sealed trait: downstream implementations would weaken invariants.
AsRef or Into inputs: caller flexibility helps without hiding semantics.
&str or &[T] inputs: prefer them over &String or &Vec<T> when the
callee only reads.
Red flags
- public APIs expose internal helper traits or incidental generic parameters,
- public APIs accept
&String or &Vec<T> where &str or &[T] would do,
- public functions take two or more
bool parameters (boolean blindness:
prefer named domain enums or an options struct),
- strings represent closed sets, validated IDs, or states that should be
enums or newtypes,
- a trait object appears only to avoid naming a concrete type,
- boxed trait objects appear even though one concrete type or
impl Trait
would work,
- one implementation spawned a deep generic or trait abstraction anyway,
- newtypes exist but immediately expose raw fields everywhere,
- invariant-carrying types expose public fields directly,
- typestate multiplies boilerplate without enforcing a real rule,
- crate features change core semantics rather than optional integration.
Read generics-vs-dyn.md,
newtypes-and-typestate.md,
public-api-boundaries.md, and
misuse-resistant-apis.md when one of
those forks becomes the main design pressure. The last covers typestate,
hidden-inner newtypes, anti-boolean-blindness, relevant API Guidelines
tags, and SemVer tooling (cargo-semver-checks, cargo-public-api).
Source: leynos/rust-skill — distributed by TomeVault.
1---2name: rust-types-and-apis3description: Use for trait bounds, generics, trait objects, newtypes, typestate, conversion boundaries, public API design, and crate-facing Rust interfaces. Use when this capability is needed.4---56# Rust Types and APIs78Use this when the real question is how much of the design should be expressed9in types, traits, and public signatures.1011## Working stance1213- Make invalid states hard to represent before adding runtime checks.14- Keep public APIs narrower than internal helper code.15- Prefer concrete types until abstraction pressure is real.16- Use trait objects for extension seams and heterogeneity, not by reflex.17- Prefer typed domains and borrowed read-only inputs over stringly or18 collection-specific signatures.19- Accept broad inputs; return precise outputs.2021## Decision surface2223- Generics: the caller should stay generic and one concrete type flows through24 each call site.25- `dyn Trait`: runtime heterogeneity, plugin seams, or monomorphization costs26 matter more than static dispatch.27- Newtype: domain distinction matters and misuse should not compile.28- Typestate: operation order is finite and important enough to encode.29- Sealed trait: downstream implementations would weaken invariants.30- `AsRef` or `Into` inputs: caller flexibility helps without hiding semantics.31- `&str` or `&[T]` inputs: prefer them over `&String` or `&Vec<T>` when the32 callee only reads.3334## Red flags3536- public APIs expose internal helper traits or incidental generic parameters,37- public APIs accept `&String` or `&Vec<T>` where `&str` or `&[T]` would do,38- public functions take two or more `bool` parameters (boolean blindness:39 prefer named domain enums or an options struct),40- strings represent closed sets, validated IDs, or states that should be41 enums or newtypes,42- a trait object appears only to avoid naming a concrete type,43- boxed trait objects appear even though one concrete type or `impl Trait`44 would work,45- one implementation spawned a deep generic or trait abstraction anyway,46- newtypes exist but immediately expose raw fields everywhere,47- invariant-carrying types expose public fields directly,48- typestate multiplies boilerplate without enforcing a real rule,49- crate features change core semantics rather than optional integration.5051Read [generics-vs-dyn.md](references/generics-vs-dyn.md),52[newtypes-and-typestate.md](references/newtypes-and-typestate.md),53[public-api-boundaries.md](references/public-api-boundaries.md), and54[misuse-resistant-apis.md](references/misuse-resistant-apis.md) when one of55those forks becomes the main design pressure. The last covers typestate,56hidden-inner newtypes, anti-boolean-blindness, relevant API Guidelines57tags, and SemVer tooling (`cargo-semver-checks`, `cargo-public-api`).5859---60> Source: [leynos/rust-skill](https://github.com/leynos/rust-skill) — distributed by [TomeVault](https://tomevault.io).61<!-- tomevault:4.0:skill_md:2026-06-16 -->