SKILL.md
Rust Trait API Design
Use this skill to make Rust APIs generic where useful, concrete where simpler, and object-safe when dynamic dispatch is part of the design. Optimize for the smallest capability contract that callers and implementors can understand.
Core Workflow
- Identify whether the API consumes, borrows, returns, stores, or dispatches
behavior.
- Start with concrete types for local code. Generalize only when at least two
callers or implementations need the flexibility.
- Use generic bounds for compile-time polymorphism and inlining. Use `dyn
Trait` for heterogeneous values, plugin-like extension points, or runtime dispatch.
- Place bounds at the point that needs them. Prefer
whereclauses when
bounds are long or involve associated types.
- Decide whether a trait is intended for downstream implementation. Seal it,
keep fields private, or use constructors when invariants must not be implemented externally.
- Use standard conversion traits where they match exactly. Do not invent a
custom conversion trait before checking From, TryFrom, AsRef, AsMut, Borrow, ToOwned, and Cow.
- Add compile tests, unit tests, or examples that prove the public API is
callable the way the skill expects future users to call it.
API Design Rules
- Accept
impl Traitor a named generic for parameters when callers should
pass many concrete types.
- Return
impl Traitwhen hiding one concrete return type. Return `Box<dyn
Trait>` when the concrete type varies at runtime.
- Prefer associated types when each implementation has one natural related
type. Prefer generic trait parameters when one implementation supports many target types.
- Implement
Fromfor infallible conversions andTryFromfor fallible
conversions. Implementing these gives callers Into and TryInto.
- Use
AsReffor cheap reference-to-reference conversion. UseBorrowonly
when borrowed and owned forms have equivalent Eq, Hash, and Ord behavior.
- Avoid
Copybounds unless the algorithm semantically requires bitwise copy.
Trait Object Review
Read references/trait-api-patterns.md when choosing between generics and dyn Trait, or when public traits fail object-safety checks.
Before making a public trait object-safe, check:
- Methods do not use generic type parameters.
- Methods do not return
Selfunless constrained withwhere Self: Sized. - Associated types are specified on the trait object where needed.
- The object is behind a pointer such as
&dyn Trait,Box<dyn Trait>, or
Arc<dyn Trait + Send + Sync>.
Common Smells
- A public function takes
&Vec<T>because it only needs iteration. - The API accepts
Stringand immediately borrows it as&str. - The API implements
Intodirectly instead ofFrom. - A trait has many blanket bounds that only one method needs.
- A trait object was used because lifetimes were confusing, not because runtime
dispatch is required.
async-traitis used in a public trait without checking whether native
async fn in traits, trait-variant, or boxed futures fit the dispatch and Send needs better.