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
rust
use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(untagged)]
Parsing flexible JSON shapes with serde
deserialization
enums
json
Intermediate
6 steps
ruby
require "shellwords" require "open3" module Backup
Building safe shell commands in Ruby
shell-out
subprocess
command-injection
Intermediate
7 steps
rust
use std::cmp::{Ordering, Reverse}; use std::collections::BinaryHeap; use std::fs::File; use std::io::{self, BufRead, BufReader, BufWriter, Lines, Write};
K-way merge of sorted logs in Rust
binary-heap
k-way-merge
streaming-io
Intermediate
8 steps
ruby
class UserAgentParser BROWSERS = [ [/Edg\/([\d.]+)/, "Edge"], [/OPR\/([\d.]+)/, "Opera"],
Parsing user-agent strings in Ruby
regex
pattern-matching
lookup-tables
Intermediate
8 steps
javascript
function evaluate(expression) { const tokens = tokenize(expression); let pos = 0;
Building a recursive descent calculator
parsing
recursion
operator-precedence
Intermediate
8 steps
rust
use axum::{ extract::{Path, State}, response::sse::{Event, KeepAlive, Sse}, };
Streaming import progress with SSE in Axum
server-sent-events
streams
watch-channel
Advanced
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.