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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Implementing FromStr gives your type free parse() and str::parse integration.
- 2Comparing tuples lexicographically is a concise way to order by multiple fields in priority order.
- 3SemVer's rule that any pre-release sorts below its release must be encoded explicitly, since None must beat Some.
Related explainers
ruby
class Order class InvalidTransition < StandardError; end TRANSITIONS = {
A state machine for order transitions in Ruby
state-machine
data-driven
error-handling
Intermediate
8 steps
rust
use std::cmp::Ordering; pub struct PrefixIndex { entries: Vec<String>,
Prefix search with binary partitioning in Rust
binary-search
sorting
case-insensitive
Intermediate
7 steps
javascript
'use client'; import { useEffect } from 'react'; import * as Sentry from '@sentry/nextjs';
How a Next.js error boundary recovers
error-boundary
error-handling
observability
Intermediate
8 steps
ruby
class Registration < ApplicationRecord belongs_to :event validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
Validating registrations in Rails
validations
i18n
error-handling
Intermediate
8 steps
ruby
class ApacheLogParser LINE_PATTERN = /\A(?<ip>\S+)\s\S+\s\S+\s\[(?<time>[^\]]+)\]\s"(?<method>[A-Z]+)\s(?<path>\S+)\s(?<protocol>[^"]+)"\s(?<status>\d{3})\s(?<bytes>\d+|-)/ TIME_FORMAT = "%d/%b/%Y:%H:%M:%S %z"
Parsing Apache logs with named captures
regex
named-captures
parsing
Intermediate
6 steps
java
public record FixedWidthField(String name, int start, int end) { public String extract(String line) { int from = Math.min(start, line.length());
Parsing fixed-width text in Java
records
parsing
streams
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/parsing-and-ordering-semantic-versions-in-rust-explained-rust-0463/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.