rust 55 lines · 8 steps

Parsing and ordering semantic versions in Rust

A SemVer struct that parses from a string and orders correctly, treating pre-release builds as older than their release.

Explained by highlit
1use std::cmp::Ordering;
2use std::str::FromStr;
3 
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct SemVer {
6 pub major: u64,
7 pub minor: u64,
8 pub patch: u64,
9 pub pre_release: Option<String>,
10}
11 
12#[derive(Debug, thiserror::Error)]
13pub enum ParseError {
14 #[error("missing {0} component")]
15 MissingComponent(&'static str),
16 #[error("invalid number in version: {0}")]
17 InvalidNumber(#[from] std::num::ParseIntError),
18}
19 
20impl FromStr for SemVer {
21 type Err = ParseError;
22 
23 fn from_str(s: &str) -> Result<Self, Self::Err> {
24 let (core, pre_release) = match s.trim_start_matches('v').split_once('-') {
25 Some((core, pre)) => (core, Some(pre.to_string())),
26 None => (s.trim_start_matches('v'), None),
27 };
28 
29 let mut parts = core.split('.');
30 let major = parts.next().ok_or(ParseError::MissingComponent("major"))?.parse()?;
31 let minor = parts.next().ok_or(ParseError::MissingComponent("minor"))?.parse()?;
32 let patch = parts.next().ok_or(ParseError::MissingComponent("patch"))?.parse()?;
33 
34 Ok(SemVer { major, minor, patch, pre_release })
35 }
36}
37 
38impl Ord for SemVer {
39 fn cmp(&self, other: &Self) -> Ordering {
40 (self.major, self.minor, self.patch)
41 .cmp(&(other.major, other.minor, other.patch))
42 .then_with(|| match (&self.pre_release, &other.pre_release) {
43 (None, None) => Ordering::Equal,
44 (None, Some(_)) => Ordering::Greater,
45 (Some(_), None) => Ordering::Less,
46 (Some(a), Some(b)) => a.cmp(b),
47 })
48 }
49}
50 
51impl PartialOrd for SemVer {
52 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
53 Some(self.cmp(other))
54 }
55}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Implementing FromStr gives your type free parse() and str::parse integration.
  2. 2Comparing tuples lexicographically is a concise way to order by multiple fields in priority order.
  3. 3SemVer's rule that any pre-release sorts below its release must be encoded explicitly, since None must beat Some.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Parsing and ordering semantic versions in Rust — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code