Cargo Semver Checks is a Rust tool by Predrag Gruevski that is tackling the problem of broken dependencies that cost developers time when trying to upgrade dependencies. Predrag’s work shows how automated checks can catch breaking changes before they’re released, potentially saving projects from unexpected failures and making dependency updates less painful across the entire Rust ecosystem.
Episode links
- Predrag’s Mastodon
- Predrag’s Blog
- “We never update unless forced to” — cargo-semver-checks 2024 Year in Review
- cargo-semver-checks issue 5
This episode is also available as a podcast, search for “Open Source Security” on your favorite podcast player.
Semantic Versioning
Semantic Versioning (semver) is a way to version software that uses three numbers: major, minor, and micro. The idea is to communicate the nature of changes in software releases. When developers increment the major version, it signals breaking changes that require users of the library to modify their code. Minor version changes signal added functionality in a backward-compatible way. The micro versions represent backward compatible bug fixes (this has been jokingly called the “shame” version). Semver is meant to provide a way library authors can communicate with library users what to expect.
Breaking everything
Predrag has a pretty awesome statistic about how often Rust Crate updates break things. More than 1 in 6 of the top 1000 most popular Rust packages has accidentally violated semantic versioning. This isn’t just a theoretical concern, it translates to 1 or 2 broken releases every single week.
What makes this stat extra impressive is that these aren’t rookie mistakes. These problems occur even among the most experienced, careful Rust developers who understand the consequences of breaking semantic versioning. It’s a people problem really, no matter how good anyone is at programming, mistakes will be made. So let’s use some tools to help.
The conversation perfectly captures the all-too-familiar developer experience: Pin dependencies and never update them because if you do, everything breaks. Security vulnerabilities force updates, and it’s painful. Nobody got promoted for updating dependencies, so don’t fix this process to avoid the pain next time it happens. It’s a technical debt spiral, or maybe a hamster wheel.
What Makes Semver Violations So Common?
The deeper insight from the conversation is just how remarkably difficult it is to detect breaking changes. Even Predrag, who’s spent years focused on this problem, admits he’s nowhere close to understanding all of the possible ways that you could break something. Rust is a much better defined language than many others are and it has a ton of complexity that cargo-semver-checks has to try and tease out. It seems pretty obvious that if it’s this hard to build a tool, developers aren’t going to figure this out easily.
How cargo-semver-checks Solves This
Predrag’s tool, cargo-semver-checks, provides a pretty simple solution for developers. cargo-semver-checks can automatically analyze package changes to detect potential semver violations. The idea is to look for known changes that would break semver promises. While the implementation is complex, the idea is pretty simple. Run a tool, and it tells you if you’re not changing the correct semver number.
What about sustainability?
The conversation with predrag also touches on deeper themes about open source sustainability. The funding challenges for tools that provide diffuse but substantial value are very real and not a solved problem today. Today cargo-semver-checks isn’t a sustainable project. It’s easy to imagine how expensive it is when dependencies break semver. Yet like most open source, it’s hard to get funding for stuff like this. While the Rust foundation does fund some of this development, it’s not nearly enough. The Foundation has many responsibilities, and not enough funding to go around. Ultimately, companies using Rust have to step up and fund cargo-semver-checks directly. Less breakage in the ecosystem means more of their engineers’ time is spent building their products, instead of diagnosing and working around whichever dependency becomes broken this week or next.
So what now?
Even if you’re not a Rust developer, the principles apply across ecosystems. The universal pattern of “we never update unless forced to” shows how improved tooling around semantic versioning could benefit every language ecosystem. Predrag mentioned he has a tool that can do some of this for Python, but the sustainability problem is what’s holding back a release. As he rightly points out, it would be irresponsible to just release a tool like this and not have a plan for active development.
I’m also pretty excited to see cargo-semver-checks enabled by default in the Rust Crates ecosystem. If every package update runs this automatically, that will be a huge step forward.