Regex in Rust the regex Crate and Its Guarantees Explained
The regex crate guarantees linear time matching by leaving out backreferences and lookaround. See the API, the compile-once rule and what to use when you need the missing features.

Regex in Rust looks like regex anywhere, with two differences that matter: it is compiled once into an automaton, and it will never take exponential time. The price is a missing feature set. This post covers the API you use daily, the compile-once rule, and what to do when the syntax you want is not there. It is a core tool for the command line tool series, for example in a log analyzer.
regex 1, Rust 1.90.
Match and capture
use regex::Regex;
use std::sync::LazyLock;
static DATE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})").unwrap());
fn main() {
let text = "released 2025-09-18, patched 2025-10-02";
println!("{}", DATE.is_match(text));
if let Some(caps) = DATE.captures(text) {
println!("{} {} {}", &caps["year"], &caps["month"], &caps["day"]);
}
let all: Vec<&str> = DATE.find_iter(text).map(|m| m.as_str()).collect();
println!("{all:?}");
println!("{}", DATE.replace_all(text, "$day/$month/$year"));
}It prints true, then 2025 09 18, then ["2025-09-18", "2025-10-02"], then released 18/09/2025, patched 02/10/2025. The r"..." raw string avoids doubling backslashes. The unwrap on a literal pattern is fine: an invalid literal is a bug caught the first time the program runs.
Compile once
Regex::new parses and compiles the pattern, which can cost microseconds to milliseconds depending on size. A Regex::new inside a loop that handles a million lines compiles a million times. Put it in a LazyLock static as above, or in a struct field built at startup.
The guarantee and its cost
The engine is an automaton, so matching time grows linearly with the input. A pattern like ^(a+)+$ that makes a backtracking engine hang on aaaa...b is harmless here. That makes it safe to run user supplied patterns with a size limit (RegexBuilder::size_limit). The limitation: no backreferences (a repeated group such as a doubled letter) and no lookaround ((?=...)). The compile error looks like regex parse error: look-around, including look-ahead and look-behind, is not supported.
When you need the missing features
| Need | Option |
|---|---|
| Lookaround, backreferences | fancy-regex, which backtracks and can be slow |
| Many patterns at once | regex::RegexSet or aho-corasick for literals |
| Non UTF-8 input | regex::bytes::Regex |
| PCRE compatibility | pcre2 crate, with C dependencies |
If a lookahead was only there to assert context, a second pass over the captures usually replaces it.
Unicode
The digit class matches any Unicode decimal digit, the word class any Unicode word character, and . any scalar value except a newline. For ASCII only, write [0-9] or turn Unicode off with (?-u). Case insensitive (?i) follows Unicode case folding.
Performance
Searching literals is optimized, and anchored patterns are cheap. For simple substring tests, str::contains is faster than a regex. For heavy loads, read the performance notes in the crate docs, and measure before tuning.
Where regex is wrong
Parsing nested structures like HTML or JSON; use a parser, or serde_json as in JSON with Serde. The API is documented in the regex crate docs, including the syntax reference.


