Open-source project
dtolnay/semver avatar
dtolnay/semver

semver: the Rust crate that follows Cargo's rules, not the spec's wording

Parser and evaluator for Cargo's flavor of Semantic Versioning

673 stars146 forksRustApache-2.0

At a glance

What is it?
The semver crate parses version numbers and evaluates version requirements using Cargo's own interpretation of Semantic Versioning, with a prerelease-aware matcher and an optional serde feature. It is deliberately narrow: for npm, RubyGems or Composer version strings, the documentation tells you to pick a different library.
Who is it for?
semver is the right crate when your Rust program needs to reason about Cargo style version requirements, that is, when the strings come from a Cargo.toml or from a registry that follows the same rules, and you want the same prerelease and comparison behaviour Cargo itself applies.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 122 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Cargo's dialect, not the specification's letter

The description line is short and precise: a parser and evaluator for Cargo's flavor of Semantic Versioning. The distinction in that phrasing is the whole point of the crate.

Semantic Versioning itself, at semver.org, is described as a guideline for how version numbers are assigned and incremented, and it is widely followed across the Cargo and crates.io ecosystem. But guidelines get interpreted, and different tools make different choices. The documentation is blunt about the consequence: where the various tools differ in their interpretation or implementation of the spec, this crate follows the implementation choices made by Cargo. If you are operating on version numbers from some other package ecosystem, you want a different semver library which is appropriate to that ecosystem.

That paragraph is worth reading twice before you add the dependency. A crate like this looks universal and is not. RubyGems and Bundler for Ruby, npm for JavaScript, Composer for PHP and CocoaPods for Objective-C all use SemVer, and each of them resolves the ambiguous parts of the spec in its own way. If your Rust service is validating a version string that came from an npm package, borrowing Cargo's rules will give you answers that look reasonable and are not the ones npm would give.

The extent of Cargo's SemVer support is documented in the Specifying Dependencies chapter of the Cargo reference, and that chapter is the authority this crate tracks.

The matcher treats prereleases as a separate universe

The example in the documentation shows a requirement and two candidate versions, and the interesting part is the first candidate.

rust
use semver::{BuildMetadata, Prerelease, Version, VersionReq};

fn main() {
    let req = VersionReq::parse(">=1.2.3, <1.8.0").unwrap();

    // Check whether this requirement matches version 1.2.3-alpha.1 (no)
    let version = Version {
        major: 1,
        minor: 2,
        patch: 3,
        pre: Prerelease::new("alpha.1").unwrap(),
        build: BuildMetadata::EMPTY,
    };
    assert!(!req.matches(&version));

    // Check whether it matches 1.3.0 (yes it does)
    let version = Version::parse("1.3.0").unwrap();
    assert!(req.matches(&version));
}

A requirement of >=1.2.3, <1.8.0 does not match 1.2.3-alpha.1 even though 1.2.3-alpha.1 sorts before 1.2.3. This is the prerelease rule: a version carrying a prerelease identifier is only matched by a requirement that explicitly opts into prereleases for that version. The practical effect for anyone building a resolver is that a plain caret or tilde requirement will not silently hand you a beta or an alpha.

Two API details in the snippet are worth noting. A Version can be assembled field by field, which is what the first candidate does, with the prerelease supplied through Prerelease::new and the build metadata set to BuildMetadata::EMPTY. Or a version can be parsed from a string with Version::parse, as the second candidate does. Both paths return a Result, so malformed input is an error rather than a silently coerced value.

Build metadata is a separate field from the prerelease, which matches the structure of the spec where build metadata is ignored for precedence but still part of the version string.

Adding the dependency and picking the features you need

The dependency declaration is the usual Cargo form:

toml
[dependencies]
semver = "1.0"

The crate is published on crates.io as semver, with documentation on docs.rs, and the crate is dual licensed under MIT and Apache-2.0, at your option.

The feature set is small and worth understanding before you add the dependency. The default feature is std, and there is a separate std feature plus a serde feature that pulls in serde as an optional dependency, renamed in the manifest to serde_core at version 1.0.220 with default features off. The crate also declares a no-std category, which matches the design: with default-features off you get a no_std build, and the serde feature is what lets you serialize a Version for storage or for a wire format.

The manifest pins a minimum Rust version of 1.68 and uses edition 2021, so the crate stays usable on older toolchains than current Rust releases require. That matters more for a library like this than for an application, because it is a dependency of other tools rather than something you run yourself.

The repository layout is compact and typical for a Rust crate: src for the implementation, tests for the test suite, benches for benchmarks with criterion 0.8 declared as a dev dependency, a fuzz directory, a build.rs that is excluded from the published package, and Cargo.toml at the root with the two licence files. The docs.rs metadata enables the serde feature and builds for x86_64-unknown-linux-gnu with extra rustdoc arguments, so the published documentation shows the serde-enabled API.

Fuzzing and benchmarks point at where the hard cases live

Two directories in the repository describe where the risk in this kind of code actually is, and both are aimed at parsing rather than at evaluation.

The fuzz directory exists, and the dev dependencies are guarded by cfg(not(miri)), so they are excluded from Miri, the Rust interpreter for undefined behaviour checks. Fuzzing a version parser is a natural fit: the input space of version strings is small in size but wide in shape, with numeric identifiers, prerelease chains, build metadata and a pile of punctuation that has to be rejected rather than accepted. A parser that silently accepts a malformed string is worse than one that errors, so a fuzz harness in the repository is a reasonable signal about how the author treats the edge cases.

The benches directory contains a benchmark named parse, configured with harness = false because criterion drives the measurement itself rather than the built in test harness. Benchmarking the parse path rather than the match path is a deliberate choice: parsing is where the allocation and branching work happens, and the evaluation of a requirement against a version is comparatively cheap once both sides are in memory.

The result is a crate that is small in surface area and tested where the failures would be. Neither the benchmark nor the fuzz corpus is described in the documentation, so the specifics of what is covered are not something the README commits to.

A stable 1.0 line, and a licence with a contribution clause

The release history shows a mature line. Version 1.0.28 was published on 2026-04-04, 1.0.27 on 2025-09-14 and 1.0.26 on 2025-03-04. The last push to the repository was on 2026-06-24, and the manifest version is 1.0.28, matching the newest release.

A 1.0 line with patch releases spread over more than a year is a reasonable signal for a library that many other tools depend on, since the version scheme in Cargo means a caret dependency on 1.0 picks up every 1.x release. The pace of the releases is irregular rather than scheduled, with gaps of a few months, so the assumption to make is that patches arrive when they are needed rather than on a calendar.

The licensing is the familiar Rust dual licence, either Apache License 2.0 or the MIT license at your option, with both files in the repository root. There is one extra clause, and it is worth knowing if you plan to contribute. Any contribution intentionally submitted for inclusion, unless explicitly stated otherwise, is dual licensed as above without additional terms or conditions. In other words, contributing does not hand over your copyright in a way that lets the project relicense your work under something else.

For a library this small, the practical adoption question is not the licence but the scope. The crate is intended specifically to implement Cargo's interpretation of Semantic Versioning, and everything else about it is in service of that one job.

Editorial conclusion

semver is the right crate when your Rust program needs to reason about Cargo style version requirements, that is, when the strings come from a Cargo.toml or from a registry that follows the same rules, and you want the same prerelease and comparison behaviour Cargo itself applies. It is the wrong crate for npm, RubyGems, Composer or CocoaPods version strings, because the documentation states plainly that where tools differ you should use a library appropriate to that ecosystem, and a version requirement that works under Cargo can mean something different there. Before you depend on it, check whether your inputs actually need Cargo semantics, and note that the crate sits at 1.0.28 with the last push on 2026-06-24, so the API you read today is the API you will get.

Frequently asked questions

How do I add the semver crate to a Rust project?

Add semver = "1.0" under [dependencies] in Cargo.toml. The default feature is std, and there is an optional serde feature for serializing versions.

Does the semver crate match prerelease versions like 1.2.3-alpha.1?

Not by default. The documented example asserts that a requirement of >=1.2.3, <1.8.0 does not match 1.2.3-alpha.1, so a prerelease is only matched when the requirement opts into prereleases.

Can I use the semver crate for npm or RubyGems version numbers?

The documentation says that where the various tools differ in their interpretation or implementation of the spec, this crate follows the implementation choices made by Cargo, and that for other package ecosystems you want a different semver library appropriate to that ecosystem.

What license is the semver crate released under?

MIT or Apache-2.0 at your option, with both LICENSE-MIT and LICENSE-APACHE in the repository. Contributions are dual licensed the same way unless stated otherwise.

Official sources

  1. dtolnay/semver on GitHub
  2. Issues
  3. License: Apache-2.0
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/dtolnay-semver.svg)](https://hysenlabs.com/projects/dtolnay-semver)

Was this useful?