Review API Design
Audit downstream construction, calls, implementations, matching, storage and
dependencies using diff, source, manifests, callers and docs, unlike the
output-only review-public-api.
Follow shared context and the
findings contract. Own public
contracts, dependencies/features and error/panic conventions, including internal
errors. Route runtime defects to review-correctness, recovery to
review-resilience, family-only naming to review-naming, signals to
review-telemetry, and standalone code/docs or docs/docs disagreements to
review-consistency. Their evidence never replaces this inventory.
Procedure
- Inventory before judging. Privately record every changed export/re-export,
trait bound, public enum variant, feature, dependency type in a signature,
serialization format and observable default. Assign each: intentional public
contract, should be narrower, needs an evolution guard, or needs investigation.
- Complete the inventory, even after finding an issue. For crate publication,
repository moves or first crates.io releases, treat the entire reachable
surface as new, including unchanged copied code. A public-method runtime bug
does not satisfy this pass.
- Apply the questions below. Establish enabled features and compile relevant
gated modules in the shipping configuration. Verify cheap compatibility
claims; return findings and coverage even when the inventory is clean.
Public-contract questions
- Visibility/layering: Does each new
pub have a downstream constructor,
caller or matcher? Use the narrowest working visibility. For private types in
public signatures or unexported public paths, export properly or narrow the
item; never hide the boundary error with #[allow(unreachable_pub)].
- Dependency exposure: Channels, SDK types and boxed futures in signatures
become semver commitments. Prefer
async fn -> T over returning a channel.
- Macros: Inventory exported attribute/derive syntax, accepted field types,
generated bounds/paths, default keys, diagnostics, hygiene and expansion-time
feature assumptions. Describe consumer-valid inputs or generated contracts,
not proc-macro internals. Public generated-code plumbing belongs on a documented
#[doc(hidden)] surface.
- Strong types: Prefer boundary newtypes (
BaseUri, Tenant(Uuid)) over
primitives; enforce their invariants fallibly at construction.
- Enums: Do variants express a matched domain contract or merely construction
history? Keep unmatched enums internal behind convenience methods. Prefer
#[non_exhaustive] for growing public enums where policy allows.
- Construction: Prefer typed setters and consuming
mut self -> Self to raw
options bags. Use constructor-family evidence. Builders avoid multiplying
constructors when required parameters vary by generic shape, but weigh a
deliberate single-entry generic constructor's ergonomics against niche
coercion needs before splitting it.
- Defaults/panics: Require convenient, safe defaults; question unexplained
new/Default or sibling divergence. Prefer const defaults to
constant-returning functions. Invalid runtime collections/configuration should
yield typed constructor errors, not panics, especially in no-crash foundations.
- Escape hatches: Make validation/classification/redaction bypasses explicit
in names and type constraints. Broad
Into<Value> contradicts a safe-primitive
restriction. Gate test-only surfaces behind the test feature.
- Threading/ownership: Check foundational/service
Send, Sync and Clone;
infectious !Send must be enforced in thread-affine guards' returned types,
not prose. For impl Trait, check ownership, lifetimes, cancellation,
thread-safety and callers' need to name/store the return type.
- Traits/derives: Are manual implementations of normally generated traits an
intended extension? Seal if not; otherwise minimize requirements and provide
defaults for evolution. Require a consumer use for derives. Removing public
impls can break consumers; check coherence/inference before calling additions
non-breaking.
Errors, dependencies and evolution
- At public/foundational boundaries prefer a crate-native canonical error struct
with
is_* accessors over many error types, leaving room for cases; relax for
internal-only crates. Classify with typed enums, not formatted text.
- Error and
expect(..) messages are lowercase, without leading capitals; say
"error", not "exception". Identify recovery-information changes for
review-resilience.
- Challenge new dependencies, especially proc-macro-heavy leaf dependencies.
Prefer std, suitable existing ecosystem/repository crates, or trivial code
over heavy derives.
- Keep features minimal, off by default with empty/minimal
default; gate fakes
and test surfaces. Require the minimum working dependency version, not
unrelated mass bumps.
- If A's types appear in B's public API, breaking A breaks B: trace and name the
crates. Dev-dependency bumps are usually consumer-invisible. Verify actual
breaks where possible; require repository versioning/release treatment for
intentional breaks.
Proof and coverage
Static code, manifests and repository rules can prove design findings without
execution. Identify the exported item and failed inventory disposition,
dependency/feature decision or internal error boundary; give decisive evidence,
concrete consumer/maintainer consequence and corrected contract or policy.
Discuss compatibility only when it establishes impact or changes the fix.
Coverage: crates/modules/API families inventoried, dependency/feature decisions
and internal error conventions reviewed, even when no findings result.
1---2name: review-api-design3description: Review a Rust change's public contract from the diff and source: visibility and layering, semver cascade, constructors, builders and defaults, strong types and enums, trait implementability and sealing, macros as public API, and error type, message and panic conventions, including internal errors. Also owns dependency and feature checks. Use for downstream contracts, manifest or error-policy changes, or review-lens's public-surface pass. Complements review-public-api, which audits an existing surface from cargo public-api output without reading source. Not for runtime defects or naming style.4---56# Review API Design78Audit downstream construction, calls, implementations, matching, storage and9dependencies using diff, source, manifests, callers and docs, unlike the10output-only `review-public-api`.1112Follow [shared context](../review-lens/review-context.md) and the13[findings contract](../review-delivery/findings-contract.md). Own public14contracts, dependencies/features and error/panic conventions, including internal15errors. Route runtime defects to `review-correctness`, recovery to16`review-resilience`, family-only naming to `review-naming`, signals to17`review-telemetry`, and standalone code/docs or docs/docs disagreements to18`review-consistency`. Their evidence never replaces this inventory.1920## Procedure21221. **Inventory before judging.** Privately record every changed export/re-export,23 trait bound, public enum variant, feature, dependency type in a signature,24 serialization format and observable default. Assign each: intentional public25 contract, should be narrower, needs an evolution guard, or needs investigation.262. **Complete the inventory**, even after finding an issue. For crate publication,27 repository moves or first crates.io releases, treat the entire reachable28 surface as new, including unchanged copied code. A public-method runtime bug29 does not satisfy this pass.303. **Apply the questions below.** Establish enabled features and compile relevant31 gated modules in the shipping configuration. Verify cheap compatibility32 claims; return findings and coverage even when the inventory is clean.3334## Public-contract questions3536- **Visibility/layering:** Does each new `pub` have a downstream constructor,37 caller or matcher? Use the narrowest working visibility. For private types in38 public signatures or unexported public paths, export properly or narrow the39 item; never hide the boundary error with `#[allow(unreachable_pub)]`.40- **Dependency exposure:** Channels, SDK types and boxed futures in signatures41 become semver commitments. Prefer `async fn -> T` over returning a channel.42- **Macros:** Inventory exported attribute/derive syntax, accepted field types,43 generated bounds/paths, default keys, diagnostics, hygiene and expansion-time44 feature assumptions. Describe consumer-valid inputs or generated contracts,45 not proc-macro internals. Public generated-code plumbing belongs on a documented46 `#[doc(hidden)]` surface.47- **Strong types:** Prefer boundary newtypes (`BaseUri`, `Tenant(Uuid)`) over48 primitives; enforce their invariants fallibly at construction.49- **Enums:** Do variants express a matched domain contract or merely construction50 history? Keep unmatched enums internal behind convenience methods. Prefer51 `#[non_exhaustive]` for growing public enums where policy allows.52- **Construction:** Prefer typed setters and consuming `mut self -> Self` to raw53 options bags. Use constructor-family evidence. Builders avoid multiplying54 constructors when required parameters vary by generic shape, but weigh a55 deliberate single-entry generic constructor's ergonomics against niche56 coercion needs before splitting it.57- **Defaults/panics:** Require convenient, safe defaults; question unexplained58 `new`/`Default` or sibling divergence. Prefer `const` defaults to59 constant-returning functions. Invalid runtime collections/configuration should60 yield typed constructor errors, not panics, especially in no-crash foundations.61- **Escape hatches:** Make validation/classification/redaction bypasses explicit62 in names and type constraints. Broad `Into<Value>` contradicts a safe-primitive63 restriction. Gate test-only surfaces behind the test feature.64- **Threading/ownership:** Check foundational/service `Send`, `Sync` and `Clone`;65 infectious `!Send` must be enforced in thread-affine guards' returned types,66 not prose. For `impl Trait`, check ownership, lifetimes, cancellation,67 thread-safety and callers' need to name/store the return type.68- **Traits/derives:** Are manual implementations of normally generated traits an69 intended extension? Seal if not; otherwise minimize requirements and provide70 defaults for evolution. Require a consumer use for derives. Removing public71 impls can break consumers; check coherence/inference before calling additions72 non-breaking.7374## Errors, dependencies and evolution7576- At public/foundational boundaries prefer a crate-native canonical error struct77 with `is_*` accessors over many error types, leaving room for cases; relax for78 internal-only crates. Classify with typed enums, not formatted text.79- Error and `expect(..)` messages are lowercase, without leading capitals; say80 "error", not "exception". Identify recovery-information changes for81 `review-resilience`.82- Challenge new dependencies, especially proc-macro-heavy leaf dependencies.83 Prefer std, suitable existing ecosystem/repository crates, or trivial code84 over heavy derives.85- Keep features minimal, off by default with empty/minimal `default`; gate fakes86 and test surfaces. Require the minimum working dependency version, not87 unrelated mass bumps.88- If A's types appear in B's public API, breaking A breaks B: trace and name the89 crates. Dev-dependency bumps are usually consumer-invisible. Verify actual90 breaks where possible; require repository versioning/release treatment for91 intentional breaks.9293## Proof and coverage9495Static code, manifests and repository rules can prove design findings without96execution. Identify the exported item and failed inventory disposition,97dependency/feature decision or internal error boundary; give decisive evidence,98concrete consumer/maintainer consequence and corrected contract or policy.99Discuss compatibility only when it establishes impact or changes the fix.100101Coverage: crates/modules/API families inventoried, dependency/feature decisions102and internal error conventions reviewed, even when no findings result.