# cargo-public-api: turning a Rust crate's public surface into a reviewable diff

> cargo-public-api is a Cargo subcommand and CI library that reads rustdoc JSON and prints or diffs the public API of a Rust crate, so a breaking change or a semver violation can be caught before a release rather than after a downstream crate fails to compile.

**cargo-public-api/cargo-public-api** — List and diff the public API of Rust library crates between releases and commits. Detect breaking API changes and semver violations via CI or a CLI.

- Repository: https://github.com/cargo-public-api/cargo-public-api
- Website: https://crates.io/crates/cargo-public-api
- Stars: 582 · Forks: 44
- Language: Rust
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/cargo-public-api-cargo-public-api

## A semver check that reads rustdoc JSON

cargo-public-api lists and diffs the public API of Rust library crates between releases and commits, and detects breaking API changes and semver violations through CI or a CLI. The mechanism underneath is rustdoc JSON, the machine readable form of rustdoc output, and the README is explicit that the tool relies on it and builds it automatically, which means a recent version of the Rust nightly toolchain must be installed. That dependency is the defining constraint of the project. The alternative to generating JSON is parsing the documentation yourself, and the alternative to diffing it is to rely on a downstream crate's compile error to tell you that you broke something. The audience is a library author who publishes to crates.io and wants the semver contract checked mechanically, and the second audience is the reviewer of a pull request who wants to see the public API change rather than infer it from the diff. The repository is MIT licensed, written in Rust, and the last push was on 2026-09-21.

## Installing the subcommand and a nightly toolchain

Installation has two halves and both are needed. The subcommand itself goes in with a regular stable toolchain.

```sh
cargo +stable install cargo-public-api
```

Then a nightly toolchain has to be present so the tool can build rustdoc JSON for you. The README requires nightly-2025-11-22 or later, and notes that it does not need to be the active toolchain, which matters because the rest of your build can stay on stable.

```sh
rustup install nightly --profile minimal
```

The minimal profile is the right choice here, since a full nightly with documentation and source components is a much larger download for a toolchain whose only job is to produce JSON. The two-step split between stable and nightly is the pattern to copy if you adopt this, because it keeps the nightly out of your normal build path and only the tool invocation needs it.

## Listing an API and diffing it against anything

The usage is three commands with different comparison targets. Listing prints the public API of the crate in the current directory, one line per public item, which the README demonstrates with the regex crate. You clone the crate first.

```sh
git clone https://github.com/rust-lang/regex ; cd regex
```

```sh
cargo public-api
```

Diffing takes a reference. A specific published version on crates.io, in the example 1.6.0 of regex, is the check most people want.

```sh
cargo public-api diff 1.6.0
```

The word latest compares against the newest published version instead, which is what you want when the published crate is already the baseline and you are checking your working tree.

```sh
cargo public-api diff latest
```

And a range of two git refs diffs the API between two commits, which is the form that belongs in a pull request check.

```sh
cargo public-api diff ref1..ref2
```

## A snapshot test in your own crate

The CI path is the interesting one, because it turns the API into a test assertion. You add three dev dependencies.

```sh
cargo add --dev \
    rustup-toolchain \
    rustdoc-json \
    public-api
```

Then you add a test that installs a compatible nightly if it is missing, builds the rustdoc JSON, derives the public API from it, and asserts it matches the stored snapshot. The snapshot is updated by setting the UPDATE_SNAPSHOTS environment variable, and the author of that test code is stated to have placed it under CC0 and waived copyright in it so that anyone can copy it.

```rust
#[test]
fn public_api() {
    // Install a compatible nightly toolchain if it is missing.
    rustup_toolchain::install(public_api::MINIMUM_NIGHTLY_RUST_VERSION).unwrap();

    // Build rustdoc JSON.
    let rustdoc_json = rustdoc_json::Builder::default()
        .toolchain(public_api::MINIMUM_NIGHTLY_RUST_VERSION)
        .build()
        .unwrap();

    // Derive the public API from rustdoc JSON.
    let public_api = public_api::Builder::from_rustdoc_json(rustdoc_json)
        .build()
        .unwrap();

    // Assert that the public API matches the latest snapshot.
    // Run with env var `UPDATE_SNAPSHOTS=yes` to update the snapshot.
    public_api.assert_eq_or_update("./tests/snapshots/public-api.txt");
}
```

The first run needs the snapshot to exist, which you create by running the test with the environment variable set.

```sh
UPDATE_SNAPSHOTS=yes cargo test
```

After that, a plain cargo test in CI fails if the public API changed, whether the change was accidental or deliberate, and updating the snapshot is a second run with the variable set, followed by reviewing the git diff. That is the whole workflow, and it is the reason the tool is worth adding to a library rather than a binary.

## Blanket and auto trait impls are why the output is noisy

The default output includes a lot of items that most people do not care about, and the tool gives you switches to remove them. Items belonging to Blanket Implementations, Auto Trait Implementations and Auto Derived Implementations are included by default, and the README gives the examples, such as a generic Into implementation, a Sync implementation, and a Debug implementation or a derive attribute. Three flags remove them.

```sh
cargo public-api --omit blanket-impls,auto-trait-impls,auto-derived-impls
```

There is also a short form, -s for --simplified, and the README notes you can achieve the same thing with the shorthand of repeating it.

```sh
cargo public-api -sss
```

The reason this matters is the snapshot. A snapshot that includes every blanket impl is unreadable in a pull request diff, and the value of the tool comes entirely from the diff being legible to a human reviewer. The README lists the three omit flags as separate options and the simplified shorthand as the convenient equivalent, and the choice between them is a taste decision about how much of the type system noise you want in the file you commit.

## The tool and the nightly have to move together

The compatibility matrix is the part of this project most likely to cause friction in practice, and the README publishes it in full. Version 0.52.x understands the rustdoc JSON output of nightly-2025-11-22 and later. The 0.50.x to 0.51.x line covers nightly-2025-08-02 through nightly-2025-11-21, 0.49.x covers nightly-2025-07-17 through nightly-2025-08-01, 0.48.x covers nightly-2025-06-22 through nightly-2025-07-16, 0.47.x covers nightly-2025-03-24 through nightly-2025-06-21, and 0.46.x covers nightly-2025-03-16 through nightly-2025-03-23, with earlier versions pointed at a file in the release helper source. The pattern is clear: each release of the tool understands a narrow window of nightly output, because the JSON format is still being designed upstream and changes. The release history shows the tool moving quickly, with 0.50.2 in December 2025, 0.51.0 in February 2026 and 0.52.0 in May 2026. That cadence is good news and bad news at once, since a tool that tracks an unstable format has to move, and each move is a chance for your pinned nightly to stop being understood.

## Where cargo-public-api is the wrong tool

There are two cases where it does not apply. The first is a crate with no public API surface, a binary, a private crate, or anything where nothing downstream depends on your types, where the whole exercise has no subject. The second is an environment that cannot install a nightly toolchain, which rules out some locked down CI images and some corporate build policies, and it is a real constraint rather than a preference. There is also a scope limit worth naming. The tool tells you that the public API changed, and it can show you the diff, but deciding whether a change is semver compliant is still a judgement, and the README frames the CI use as preventing accidental changes and reviewing the diff of deliberate ones, not as an automatic semver verdict. An alternative worth naming is cargo-semver-checks, which is a different tool with a different approach, but this article does not cover it in detail.

## Conclusion

Adopt cargo-public-api if you publish Rust libraries and want breaking changes and semver violations caught by a test rather than by a downstream user's compile error, and if you are willing to keep a pinned nightly toolchain in your build. Do not adopt it for a binary-only crate with no public API surface, or if your CI cannot install a nightly toolchain, because the tool depends on rustdoc JSON output that only a recent nightly can produce. Verify first which nightly your pinned version of the tool understands, using the compatibility matrix, since the tool and the nightly move together.

## FAQ

### How do I install cargo-public-api?

Install the subcommand with a stable toolchain using cargo +stable install cargo-public-api, then make sure a nightly toolchain is available with rustup install nightly --profile minimal. The README requires nightly-2025-11-22 or later, and it does not need to be the active toolchain.

### How do I check my crate's public API in CI?

Add rustup-toolchain, rustdoc-json and public-api as dev dependencies, add the public_api test that builds rustdoc JSON and calls assert_eq_or_update against a snapshot file, then create the snapshot once with UPDATE_SNAPSHOTS=yes cargo test. After that a plain cargo test fails whenever the public API changes.

### What can cargo public-api diff against?

A specific published version on crates.io, as in cargo public-api diff 1.6.0, the latest published version with cargo public-api diff latest, or a range of two git references with cargo public-api diff ref1..ref2, which is the form suited to a pull request check.

### Which nightly toolchain does my version of cargo-public-api support?

The README publishes a compatibility matrix. Version 0.52.x understands nightly-2025-11-22 and later, 0.50.x to 0.51.x covers nightly-2025-08-02 through nightly-2025-11-21, and earlier versions each cover their own narrow window, with the oldest listed pointing to a file in the release helper source.

## Sources

- [cargo-public-api/cargo-public-api on GitHub](https://github.com/cargo-public-api/cargo-public-api)
- [License: MIT](https://github.com/cargo-public-api/cargo-public-api/blob/main/LICENSE)
- [Project website](https://crates.io/crates/cargo-public-api)
- [README](https://github.com/cargo-public-api/cargo-public-api/blob/main/README.md)
- [Releases](https://github.com/cargo-public-api/cargo-public-api/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/cargo-public-api-cargo-public-api
