Cargo and Package Management

Semantic Versioning

Rust and Cargo follow Semantic Versioning 2.0.0 (SemVer) (MAJOR.MINOR.PATCH), encoding API compatibility guarantees directly into version numbers so cargo update can upgrade dependencies safely without breaking downstream code.

Version Format:   2 . 4 . 1
                  |   |   |
                  |   |   +-- PATCH: Backward-compatible bug fixes & internal perf
                  |   +------ MINOR: Backward-compatible new features / APIs
                  +---------- MAJOR: Breaking API changes (or 0.MINOR.PATCH pre-1.0)

What Counts as a Breaking vs Non-Breaking Change in Rust?

Following the official Cargo SemVer reference, several seemingly small code edits in Rust are breaking changes (requiring a MAJOR version bump on >=1.0.0, or a MINOR bump on 0.y.z):

Change to Public API Breaking or Non-Breaking? Why & How to Make It Non-Breaking
Adding a new pub fn or pub struct Non-Breaking (Minor) Existing callers are unaffected.
Adding a field to a pub struct with all pub fields Breaking (Major) Breaks struct literal construction Point { x, y } and exhaustive pattern matching let Point { x, y } = p. Mark the struct #[non_exhaustive] upfront to allow adding fields later!
Adding a variant to a pub enum Breaking (Major) Breaks exhaustive match expressions in downstream crates. Mark the enum #[non_exhaustive] upfront to require a _ => wildcard arm in external callers!
Adding a method to a pub trait without a default body Breaking (Major) Breaks downstream impl MyTrait for CustomType blocks. Provide a default method implementation (fn new_method(&self) { ... }) to make it non-breaking.
Tightening generic trait bounds on a function Breaking (Major) Callers passing types that don't satisfy the new bound fail to compile (relaxing bounds is non-breaking).
Removing a Copy, Clone, Send, or Sync impl Breaking (Major) Downstream code relying on auto-traits or Clone stops compiling.

Future-Proofing Structs and Enums with #[non_exhaustive]

Applying #[non_exhaustive] to a public struct, enum, or enum variant allows your crate to add new fields or variants in future minor releases (1.1.0 -> 1.2.0) without breaking downstream callers:

/// Configuration options that may grow new fields in future minor versions.
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct ClientOptions {
    pub timeout_ms: u64,
    pub max_retries: u32,
}

impl Default for ClientOptions {
    fn default() -> Self {
        Self {
            timeout_ms: 5_000,
            max_retries: 3,
        }
    }
}

/// Server error codes that may gain new variants in future minor versions.
#[non_exhaustive]
#[derive(Debug, PartialEq, Eq)]
pub enum ApiErrorCode {
    RateLimited,
    Unauthorized,
    InternalServerError,
}

fn describe_error(code: ApiErrorCode) -> &'static str {
    // External crates MUST include a `_ =>` wildcard arm when matching a `#[non_exhaustive]` enum:
    match code {
        ApiErrorCode::RateLimited => "429 Too Many Requests",
        ApiErrorCode::Unauthorized => "401 Unauthorized",
        _ => "Other API Error",
    }
}

fn main() {
    let mut opts = ClientOptions::default();
    opts.timeout_ms = 2_500;
    println!("Options: {opts:?}");
    println!("Error:   {}", describe_error(ApiErrorCode::RateLimited));
}
Options: ClientOptions { timeout_ms: 2500, max_retries: 3 }
Error:   429 Too Many Requests

Automated SemVer Checking with cargo-semver-checks

Before publishing a new minor or patch release to crates.io, run cargo-semver-checks (cargo install cargo-semver-checks --locked). It extracts the rustdoc JSON of your current workspace and compares it against the previous published baseline on crates.io (or a Git tag) across hundreds of lint rules to catch accidental breaking changes automatically:

cargo semver-checks check-release