Public API

Interfaces and behavior that callers may depend on across a crate or system boundary.

70 items in this guidance cluster.

Tagged guidance

Guide item

Agent workflow rules

Boundary rules

Change shape rules

Documentation rules

Rust rules

Avoid giant crate rootsUse the crate root to teach the public shape and route readers to focused modules. This keeps lib.rs or main.rs from becoming the whole implementation surface.Avoid glob re-exportsRe-export public facade names explicitly instead of using globs. This prevents accidental API expansion and makes exported names visible during review.Avoid public dependency couplingKeep dependency types out of public APIs unless interoperability is the purpose. This preserves semver freedom and avoids forcing downstream users onto implementation choices.Central item firstPut the main type, trait, enum, or function before supporting details. Readers can learn the modules purpose before chasing helpers and adapters.Choose generics and trait objects deliberatelyPick generics, stored type parameters, or trait objects for the variation they actually model. The choice affects compile cost, object safety, lifetimes, and caller ergonomics.Consider downstream API impactCheck public API changes against downstream imports, traits, inference, and examples before reshaping them. Additive paths and deprecations often avoid unnecessary breakage.Do not default pub(crate)Start items private and widen to pub(crate) only for deliberate shared internals. This keeps modules independent and makes crate-local contracts visible.Do not pin patch versionsKeep manifest requirements as wide as the crate honestly supports. Patch pins belong in Cargo.toml only when code depends on that patchs API, fix, or behavior.Document current implemented behaviorDocument what the crate does today instead of presenting future plans as available contract. Clear tense and labels prevent callers from relying on unimplemented behavior.Document feature contractsExplain what each feature flag enables, requires, and promises. Feature contracts help users choose combinations without guessing from dependency names.Document performance contractsState meaningful performance expectations when callers may design around them. Clear limits keep complexity and optimization claims tied to supported behavior.Document visibility ownershipPair widened visibility with names and docs that identify the owning concept. This prevents shared internals from looking like accidental stable API.Expose primary path from crate rootMake the crate root show the main workflow, types, and import path. Users should not have to infer the intended entry point from private layout details.Group private imports before public re-exportsSeparate implementation imports from public re-exports in module prologues. The grouping lets readers distinguish internal dependencies from the API surface being presented.Hide test-only helpersKeep fixtures and shortcuts behind test-only modules, features, or support crates unless they are deliberate API. This prevents scaffolding from leaking into production contracts.Implement debug for public typesProvide Debug for public types unless it would expose secrets or mislead callers. The trait is baseline support for tests, assertions, logs, and downstream diagnostics.Implement standard traits for public errorsMake reusable public errors implement the standard diagnostic traits where appropriate. This lets callers compose, display, chain, and inspect errors in ordinary Rust workflows.Keep crate boundaries narrowPut behavior and tests in the crate or module that owns them before extracting shared helpers. Narrow boundaries reduce dependency fan-out, feature pressure, and hidden coupling.Keep pre-release compatibility intentionalPreserve pre-release compatibility only when it reflects a chosen contract. Early cleanup is often cheaper than freezing accidental names, re-exports, features, or variants.Keep preludes re-export onlyPut only re-exports in prelude modules and keep original behavior in its owning module. Users expect preludes to aid imports, not hide implementation ownership.Keep public API shape intentionalMake public visibility, aliases, features, re-exports, bounds, and variants reflect intended commitments. Published surface area becomes something downstream users can depend on.Make feature flags additive where possibleDesign feature flags as additive capabilities whenever possible so Cargo feature unification does not surprise downstream builds. Make incompatible combinations explicit when addition cannot model the real choice.Make public API browseable from layoutAlign public modules, re-exports, and source files so readers can navigate from API to ownership without translation. Facades are fine when they improve discovery and still point toward the owning concept.Non exhaustive public errorsMark public error enums non-exhaustive unless exhaustive matching is part of the contract. This preserves room for future integration, validation, or provider failures without needless downstream breakage.Order items for API readingOrder imports, public items, impls, trait impls, and helpers so the API story is easy to scan. Respect macros, generated code, and local convention when another order communicates better.Prefer constructors and conversion traitsUse inherent constructors and standard conversion traits to show whether construction builds, validates, converts, borrows, allocates, or can fail. Prefer public fields only when direct construction is truly part of the contract.Reexport for discoveryRe-export public items where callers naturally look so the crate surface is discoverable without hiding ownership. Keep canonical definitions and docs clear so re-exports do not become competing homes.Teach crate from crate rootUse crate-root docs and exports to teach the crates main concepts, entry points, and common paths. Keep the root focused enough to orient readers without duplicating every item-level contract.Tie optional dependencies to named featuresConnect optional dependencies to clear feature names that explain the capability callers enable. Avoid leaking dependency names as the public feature design when the capability needs a more stable contract.Use builders for optional or validated fieldsUse builders when construction has many optional inputs or cross-field validation that would make constructors hard to read. Avoid builder APIs for simple values where direct construction communicates the contract better.Use doc inline for canonical reexportsUse #[doc(inline)] when a re-export should be the canonical place readers encounter an item. Avoid inlining re-exports that would obscure the owning module or create duplicate-looking documentation.Use honest minimum dependenciesSet dependency requirements to the lowest compatible versions the crate actually supports. Raise minimums only for required APIs, fixes, features, security needs, or MSRV interactions.Use meaningful standard typesPrefer standard or ecosystem types that encode ownership, units, paths, durations, optionality, and invariants better than raw strings or integers. Use domain newtypes when the standard type cannot prevent meaningful mixups.Use send static across tasksRequire owned Send + static values, futures, errors, and handles when they cross spawn or thread boundaries. Avoid imposing those bounds on local synchronous APIs where they would reject valid use.Validate semver breaks against external useCheck semver-breaking changes against real examples, dependents, or migration paths before treating an API cleanup as cheap. External evidence informs the cost even when security, soundness, or design repair still justify the break.Write public docs for caller tasksWrite public Rustdoc around what callers are trying to decide, do, and rely on. Start with concise behavior and add arguments, failures, lifecycle, features, links, or examples only when they help the task.Write rustdoc as API contractUse Rustdoc to state caller-facing behavior, invariants, failures, side effects, and compatibility promises. Leave private implementation detail in comments unless it helps maintain the public contract.

Source rules

Testing rules

Pattern items

Avoid boolean flag parametersBehavioral boolean flags make call sites depend on hidden ordering and branch semantics. Distinguish them from boolean domain data, then prefer named operations, enums, options types, or builders when the caller selects behavior.Document errors panics safetyCallers need to know which failures are recoverable, which conditions panic, and what safety obligations an API imposes. Document those contracts at the public boundary so users can compose the API deliberately.Keep public dependencies intentionalPublic dependencies shape downstream compatibility, API expectations, and maintenance cost. Add or raise them only when the public behavior needs that requirement, using lockfile updates for ordinary compatible refreshes.Make invalid states hard to expressRepeated validation spreads invariants across callers and lets unchecked values travel too far. Move the rule into a construction boundary or precise type when that reduces downstream reasoning without adding local ceremony.Parse dont validateValidation that returns raw values forces callers to remember which invariants already hold. Parse uncertain input into types that carry those invariants, using constructors or standard parsing traits that honestly match the boundary.Prefer standard conversionsProject-specific conversion names make APIs harder to predict when standard traits already describe the relationship. Use standard conversions only when their semantics are honest, and choose explicit domain methods for lossy, contextual, or surprising operations.Return structured errorsString-only errors hide the stable facts callers need for branching, retrying, logging, and support. Return structured error values with machine-readable kinds and context, then render audience-specific messages at the boundary.Run rustdoc quality passUse a bounded checklist when raising a Rust crates public documentation. The pass should teach the crate model, document public contracts, place examples where they prove real use, keep documentation layers aligned, and validate Rustdoc as code.Write actionable error messagesAccurate error messages can still leave users, operators, callers, or support unable to act. Design failure output so the audience can find the attempted operation, affected item, known cause, impact, next step, and diagnostic handle when those facts matter.Write docs as contractsBehavior and obligations become surprising when they are only discoverable by reading implementation details. Treat docs as part of the change surface, updating contracts and rationale alongside the code they describe.

Principle items

Mechanism item

Agent item