rust 40 lines · 8 steps

Building URL slugs in Rust

Turn arbitrary titles into clean, collision-free URL slugs by lowercasing, transliterating accents, and appending numeric suffixes.

Explained by highlit
1use once_cell::sync::Lazy;
2use regex::Regex;
3 
4static NON_ALPHANUMERIC: Lazy<Regex> = Lazy::new(|| Regex::new(r"[^a-z0-9]+").unwrap());
5 
6fn slugify(title: &str) -> String {
7 let lowered = title.to_lowercase();
8 
9 let normalized: String = lowered
10 .chars()
11 .map(|c| match c {
12 'à' | 'á' | 'â' | 'ã' | 'ä' | 'å' => 'a',
13 'è' | 'é' | 'ê' | 'ë' => 'e',
14 'ì' | 'í' | 'î' | 'ï' => 'i',
15 'ò' | 'ó' | 'ô' | 'õ' | 'ö' => 'o',
16 'ù' | 'ú' | 'û' | 'ü' => 'u',
17 'ñ' => 'n',
18 'ç' => 'c',
19 'ß' => 's',
20 other => other,
21 })
22 .collect();
23 
24 let slug = NON_ALPHANUMERIC.replace_all(&normalized, "-");
25 slug.trim_matches('-').to_string()
26}
27 
28fn slugify_unique(title: &str, existing: &[String]) -> String {
29 let base = slugify(title);
30 if base.is_empty() {
31 return "untitled".to_string();
32 }
33 if !existing.iter().any(|s| s == &base) {
34 return base;
35 }
36 (2..)
37 .map(|n| format!("{base}-{n}"))
38 .find(|candidate| !existing.iter().any(|s| s == candidate))
39 .unwrap()
40}
01 / 01
STEP 01

Walkthrough

Space play ←→ step click any line
Three takeaways
  1. 1A lazily compiled static regex avoids recompiling the pattern on every call.
  2. 2Transliterating accented characters before stripping non-alphanumerics keeps slugs readable instead of dropping letters.
  3. 3An unbounded numeric range makes finding a unique suffix a simple, allocation-light search.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building URL slugs in Rust — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code