Rust Regex in Practice
Good regex practice is two habits that keep regex code fast and honest. Build each pattern once and reuse it, and reach for a pattern only when the job really has a shape.
Build once, use many times
One matcher answers every question. Nothing is rebuilt between calls.
let re = Regex::new(r"\d+").unwrap();
println!("{} {}", re.is_match("a1"), re.is_match("bc"));A LazyLock static builds on first use
The static holds the matcher for the whole program. The use lines sit at the top of the file, as usual.
static DIGITS: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\d+").unwrap());
println!("{}", DIGITS.is_match("room 101"));RegexSet checks many patterns at once
The set answers with pattern numbers. 0 means the first pattern matched. 1 means the second did.
let set = RegexSet::new([r"\d+", r"[A-Z]+"]).unwrap();
println!("{:?}", set.matches("abc1").into_iter().collect::<Vec<usize>>());
println!("{:?}", set.matches("ABC").into_iter().collect::<Vec<usize>>());Fixed text needs no regex
These questions have no shape to describe. Plain methods answer them directly.
let line = "status: ok";
println!("{}", line.starts_with("status:"));
println!("{}", line.contains("ok"));An email check checks shape, not truth
This pattern asks for the email shape. A match can still be a fake address. Only sent mail proves an address real.
let re = Regex::new(r"^[^@\s]+@[^@\s]+\.[^@\s]+$").unwrap();
println!("{}", re.is_match("ferris@example.com"));
println!("{}", re.is_match("not an address"));Example
// Cargo.toml: regex = "1"
use regex::{Regex, RegexSet};
use std::sync::LazyLock;
static CODE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^[A-Z]{2}\d{4}$").unwrap());
fn main() {
for code in ["AB1234", "ab1234", "ABC123"] {
println!("{code} ok = {}", CODE.is_match(code));
}
let banned = RegexSet::new([r"free", r"winner"]).unwrap();
println!("spammy = {}", banned.is_match("you are a winner"));
}Key points
- The two habits in one line: build once, and match shapes only.
- Regex::new does real work: it compiles the pattern.
- Building a Regex inside a loop pays that cost on every pass.
- Build the Regex once and reuse it: pass it to the code that needs it, or park it in a static
- LazyLock builds its value the first time it is used.
- After that, the value just sits there, ready.
- RegexSet tests many patterns in one pass.
- RegexSet reports which patterns matched, not where they matched.
- Fixed text needs no pattern.
- starts_with, contains, and split with a plain char are clearer and faster.
In this example, one matcher is built once and shared. The LazyLock static builds the code pattern on its first use. Three candidate codes are tested against it. Only AB1234 fits the shape: two capital letters, then four digits. Then a RegexSet scans one line for two banned words at once. The word winner is in there, so the line counts as spammy.
Note
The regex crate never backtracks.
Match time grows in a straight line with the text size.
The price is honesty about features: no backreferences, and no look-around.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
Why should a Regex be built once and reused?