Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Public API Evolution

Rule

Apply compatibility discipline to published or independently consumed APIs; optimize coordinated application internals for current simplicity.

Why

External callers update independently, while internal callers can move in the same change. Compatibility machinery helps the former and burdens the latter.

Do

  • Classify an API as coordinated internal code or independently consumed public code.
  • Keep published surfaces small and deliberate.
  • Follow semver for externally consumed APIs and record intentional breaks.
  • Consider #[non_exhaustive] when a new public enum or struct is expected to grow.
  • Use deprecation periods when consumers cannot migrate atomically.
  • Add #[must_use] when directly discarding a returned value is almost always a bug.
  • Follow owner pages for fields, traits, and dependencies.

Avoid

  • Do not add compatibility shims or #[non_exhaustive] to coordinated internal code by habit.
  • Do not add #[non_exhaustive] to an existing public type without treating that addition as breaking.
  • Do not remove or change published APIs without compatibility review.
  • Do not rely on clippy::must_use_candidate to choose semantic policy.
  • Do not assume #[must_use] enforces cleanup after a value has been bound.

Library vs Application

Applications update callers together and delete stale APIs. Published crates and independently consumed interfaces use semver-aware evolution.

Example

#![allow(unused)]
fn main() {
#[non_exhaustive]
pub enum ClientError {
    Timeout,
    Unauthorized,
}

/// Owns a background task that should be shut down explicitly.
#[must_use = "call `shutdown` to stop and join the task"]
pub struct Poller {
    task: JoinHandle<Result<(), PollerError>>,
}
}

#[must_use] catches start_poller();, but not let poller = start_poller(); drop(poller);.

Exceptions

  • Keep temporary compatibility shims for staged migrations.
  • Use #[non_exhaustive] internally only when it materially clarifies active development.