- name
- rust-api-design
- license
- Apache-2.0
- description
- Design Rust library APIs that follow the Rust API Guidelines — naming (C-CASE, C-CONV, C-GETTER), interop traits (C-COMMON-TRAITS, C-CONVERT, C-ITER, C-SERDE), predictability (C-INTUITIVE, C-CONST), flexibility (C-GENERIC, C-NEWTYPE, C-EXT), type safety (C-BOOL, C-NONZERO, C-WRAPPER, C-STR), dependability (C-PANIC, C-UNWRAP), debuggability (C-DEBUG), and future-proofing (C-SEALED, C-STRUCT-FIELD, C-NON-EXHAUSTIVE). Use when users design a public crate API, choose between generics/concrete/newtype, decide trait bounds, hide implementation, avoid breakage, or ask "what is idiomatic Rust API design"; hand semver and publish workflow to rust-semver, lint config to rust-style-clippy, and in-crate layout to rust-module-layout.
# Rust API Design (Rust API Guidelines)
> Authority: [Rust API Guidelines](https://rust-lang.github.io/api-guidelines/about.html) — the de-facto standard checklist of ~100 C-* rules used by std, tokio, serde, bevy. See [the full checklist](references/api-guidelines-checklist.md) for canonical wording of every rule.
This skill owns the **shape of a public Rust API**: types, traits, naming, conversions, and the boundaries that keep the API usable, ergonomic, and resistant to breakage. It does **not** own Cargo manifest (`rust-cargo-build`), workspace topology (`rust-workspace`), in-crate `src/` layout (`rust-module-layout`), or semver/publish (`rust-semver`).
## Capability Boundaries
### ✅ Strengths
1. Translating API Guidelines' ~100 C-* rules into concrete design decisions for a public crate
2. Naming types, functions, methods, and features to match std and ecosystem conventions
3. Choosing between generics, trait objects, concrete types, and the newtype pattern
4. Designing `From`/`Into`/`TryFrom`/`AsRef`/`Borrow` conversions (rejecting `Deref` polymorphism)
5. Picking which auto traits (`Debug`, `Clone`, `Eq`, `Hash`, `Send`, `Sync`) to derive
6. Sealing traits, marking `#[non_exhaustive]`, hiding struct fields — to reserve room to evolve
7. Replacing bool parameters with enums, raw integers with newtypes, `String` with `&str`
8. Designing iterators, `Extend`, `Default`, `Display`/`FromStr` correctly
9. Avoiding `panic!`/`unwrap`/`expect` in public APIs in favor of `Result`
### ⚠️ Prerequisites
1. Rust ownership, traits, lifetimes — see `rust-stable`
2. Module visibility and re-exports — see `rust-module-layout`
3. Cargo manifest for `[features]`, `optional = true` — see `rust-cargo-build`
### ❌ Out of Scope
1. Semver rules and publish workflow → `rust-semver`
2. Clippy lint configuration → `rust-style-clippy`
3. Doctests and rustdoc comments → `rust-documentation`
4. Workspace topology → `rust-workspace`
## Data Privacy
This skill does not collect, store, or transmit user data.
---
# The Eight Chapters (One Section Per API Guidelines Chapter)
The Guidelines are organized into 11 chapters. This skill owns 8 — the design chapters. Documentation, Macros, and Necessities live in `rust-documentation`, `rust-macros`, and `rust-cargo-build` respectively.
## 1. Naming (C-CASE, C-CONV, C-GETTER, C-NAMING)
### C-CASE — casing conventions
| Item | Case | Example |
|------|------|---------|
| Types (struct/enum/trait), modules, crates | `UpperCamelCase` | `HttpClient`, `tokio` |
| Functions, methods, locals, fields | `snake_case` | `send_request` |
| Constants, statics | `SCREAMING_SNAKE_CASE` | `MAX_RETRIES` |
| Generic types | single `UpperCamelCase` letter or short word | `T`, `K`, `V`, `Req` |
| Lifetimes | short `'a`/`'b` or descriptive `'src` | `'src` |
| Features | `kebab-case` (Cargo enforces) | `serde-json` |
Avoid ad-hoc abbreviations: `BufferedReader` is std-compliant; `BufRdr` is not.
### C-CONV — conversion method naming
| Conversion | Prefix | Borrows? | Example |
|-----------|--------|----------|---------|
| Cheap, borrowed | `as_` | Yes (`&self → &T`) | `as_slice`, `as_bytes` |
| Cheap, owned | `to_` (no alloc) / `into_` (consumes self) | Varies | `to_vec`, `into_bytes` |
| Expensive, returns new | `to_` | No (`&self → T`) | `to_lowercase` |
| Consuming | `into_` | No (`self → T`) | `into_string`, `into_iter` |
| Fallible | `TryFrom`/`TryInto` | — | `u32::try_from(byte)` |
Requirements: **(1)** A method named `as_X` that returns an owned `X` is wrong — rename to `to_X`. **(2)** The matched inverse for mutable borrows is `as_X_mut` / `X_mut`.
### C-GETTER — accessor naming
```rust
// ✅ C-GETTER compliant
pub struct Buffer { data: Vec<u8> }
impl Buffer {
pub fn data(&self) -> &[u8] { &self.data } // no get_ prefix
pub fn data_mut(&mut self) -> &mut [u8] { &mut self.data }
pub fn len(&self) -> usize { self.data.len() } // not get_len
pub fn is_empty(&self) -> bool { self.data.is_empty() }
}
```
Exceptions where `get_` is allowed: `Cell::get`, `Map::get` (genuine lookup semantics).
## 2. Interoperability (C-COMMON-TRAITS, C-CONVERT, C-ITER, C-SERDE)
### C-COMMON-TRAITS — derive the obvious traits
For every public type, ask: should this derive `Debug`, `Clone`, `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `Hash`, `Default`?
| Trait | Default | Exception |
|-------|---------|-----------|
| `Debug` | Yes | Secrets → manual `Debug` that redacts |
| `Clone` | Yes if cheap | Expensive clone → omit + document |
| `Copy` | Only ≤16 bytes + lossless | — |
| `PartialEq`/`Eq` | Yes if total order | `f64` cannot be `Eq` |
| `Hash` | Yes if `Eq` | Must agree with `Eq` |
| `Default` | Yes if natural empty/zero | — |
### C-CONVERT — `From`/`Into`/`AsRef`/`Borrow`; reject `Deref` polymorphism
```rust
// ✅ Impl From<T> for U; Into comes for free
impl From<ErrorKind> for Error { fn from(k: ErrorKind) -> Self { Error::Kind(k) } }
// ✅ AsRef for borrowed views
impl AsRef<str> for Name { fn as_ref(&self) -> &str { &self.0 } }
// ✅ Borrow<str> if Eq/Hash should agree with str
impl Borrow<str> for Name { fn borrow(&self) -> &str { &self.0 } }
```
**Reject `Deref` as polymorphism**: `Deref` is for smart pointers (`Box`, `Rc`, `Arc`, `String` — std precedent). Using `Deref` to make `MyClient` transparent to `HttpClient` gives hidden method injection, breaks `&self` resolution, and is the **deref polymorphism anti-pattern**. Use `AsRef`, an explicit method, or composition.
### C-ITER — iterator design
```rust
// ✅ Provide IntoIterator for &T, &mut T, T when sensible
impl<'a> IntoIterator for &'a Grid {
type Item = &'a Cell;
type IntoIter = std::slice::Iter<'a, Cell>;
fn into_iter(self) -> Self::IntoIter { self.cells.iter() }
}
impl Grid {
pub fn iter(&self) -> impl Iterator<Item = &Cell> { /* */ }
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Cell> { /* */ }
}
impl Extend<Cell> for Grid { /* */ }
```
Rules: `iter()` borrows, `iter_mut()` mutably borrows, `into_iter()` consumes. Never return `Vec` from iteration methods — return `impl Iterator`.
### C-SERDE — serialization interop
- Use `#[serde(rename_all = "kebab-case")]` consistently within a type
- `#[non_exhaustive]` enums need `#[serde(other)]` for forward compat with unknown variants
- Re-export serde under a feature (`serde = ["dep:serde"]`) so downstream can opt out
## 3. Predictability (C-INTUITIVE, C-CONST, C-COMMON)
### C-INTUITIVE — naming reflects semantics
If the caller had to read the source to know what `read` does, the API is wrong. Don't surprise: a `read` method that panics on EOF is a defect.
### C-CONST — `const fn` where possible
```rust
pub const fn new(value: u32) -> Self { Self(value) }
const fn is_power_of_two(n: u32) -> bool { n != 0 && n & (n - 1) == 0 }
```
Every std lib API that can be `const fn` is a candidate. Enables `const MAX: UserId = UserId::new(1000);` in callers.
### C-COMMON — sensible defaults via `Default` and builder
```rust
impl Default for Config {
fn default() -> Self {
Config { retry_count: 3, timeout: Duration::from_secs(30) }
}
}
// Builder for complex construction
let client = Client::builder().with_retry(3).with_timeout(Duration::from_secs(10)).build()?;
```
## 4. Flexibility (C-OVERLOAD, C-GENERIC, C-NEWTYPE, C-EXT)
### C-GENERIC — generics on input, concrete on output
```rust
// ✅ Generic over AsRef<str> — caller passes &str, String, Cow
pub fn parse(input: impl AsRef<str>) -> Result<Foo> { /* */ }
// ❌ Concrete &str — forces caller to borrow
pub fn parse(input: &str) -> Result<Foo> { /* */ }
```
Trade-off: more generics → longer compile, harder diagnostics. Generic on input types (`AsRef<str>`, `IntoIterator`); concrete on output.
### C-NEWTYPE — wrap primitives to prevent misuse
```rust
// ✅ Newtypes around raw primitives
pub struct UserId(pub u64);
pub struct Email(String); // private inner — can't be constructed unsafely
fn delete_user(id: UserId) { /* */ } // can't accidentally pass a PostId
// ❌ Plain primitives — confusion and argument-order bugs
fn delete_user(id: u64) { /* */ }
```
Zero-cost (compile to the underlying type), prevent argument-order bugs, and allow attaching methods (`UserId::is_anonymous()`).
### C-EXT — extension traits via `Ext` suffix
```rust
pub trait StringExt { fn slugify(&self) -> String; }
impl StringExt for str { fn slugify(&self) -> String { /* */ } }
// Caller opts in:
use my_crate::StringExt;
"Hello World".slugify();
```
Don't put methods directly on `String`/`Vec`/`HttpRequest` from other crates — use an `Ext` trait.
## 5. Type Safety (C-BOOL, C-NONZERO, C-STR, C-SIGNED, C-BITFLAG, C-WRAPPER, C-INTERVAL)
> Authority: [API Guidelines — Type Safety](https://rust-lang.github.io/api-guidelines/type-safety.html). See `references/api-guidelines-checklist.md` for canonical wording.
### C-BOOL — replace bool parameters with enums
```rust
// ✅ Enum — caller intent is explicit at call site
pub enum Trim { Whitespace, None }
pub fn parse(input: &str, trim: Trim) -> Result<Foo> { /* */ }
parse(" x ", Trim::Whitespace);
// ❌ Bool — caller must remember what true means
pub fn parse(input: &str, trim: bool) -> Result<Foo> { /* */ }
parse(" x ", true); // true = ??
```
Two bool params compound: `f(true, false, true)` is incomprehensible. Two-arg enums are the floor. Set `clippy.toml` `max-fn-params-bools = 1` and `max-struct-bools = 1` to enforce mechanically.
### C-NONZERO — `NonZeroUsize` when zero is invalid
```rust
use std::num::NonZeroUsize;
// ✅ NonZeroUsize encodes "≥ 1" in the type
pub fn chunk_size(&self) -> NonZeroUsize { /* */ }
```
Enables niche optimization: `Option<NonZeroU32>` is the same size as `u32`. Use `NonZeroU8`/`NonZeroU16`/`NonZeroU32`/`NonZeroU64`/`NonZeroUsize` and the `NonZeroI*` variants.
### C-STR — `&str` not `&String`; `&[T]` not `&Vec<T>`
```rust
// ✅ Borrow slices for inputs
pub fn process(data: &[u8], name: &str) { /* */ }
// ❌ Forces caller to have owned collections
pub fn process(data: &Vec<u8>, name: &String) { /* */ }
```
### C-SIGNED — prefer unsigned types when values can't be negative
```rust
// ✅ u64 — semantically "count" can't be negative
pub struct Counter { count: u64 }
// ❌ i64 — implies negative values are valid (they aren't)
pub struct Counter { count: i64 }
```
For special ranges (e.g., `Age` 0..=150), use a newtype with validating constructor — let the type system prevent invalid values.
### C-BITFLAG — use the `bitflags!` macro for flag sets
```rust
use bitflags::bitflags;
bitflags! {
Ver en GitHub