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