Regular expressions in Rust
last modified October 25, 2026
In this article we show how to use regular expressions in Rust.
Regular expressions are patterns that describe sets of strings.
They are used for searching, extracting, replacing, splitting, and validating
text. The Rust standard library does not contain a regex engine; the
regex crate, maintained by the Rust project, is the standard
choice.
The regex crate guarantees that searching takes time linear in
the size of the pattern and the input. To achieve this, it does not support
look-around assertions and backreferences, which may need exponential time
in backtracking engines. The patterns are Unicode-aware by default.
The examples were tested with Rust 1.97, the 2024 edition, and
regex 1.13.1. They are run as Cargo projects with
cargo run -q. We add the crate with cargo add regex.
[dependencies] regex = "1.13.1"
The dependency is listed in the Cargo.toml file.
The following table shows common regular expression syntax.
| Regex | Meaning |
|---|---|
. |
Matches any character except a newline. |
? |
Matches the preceding element once or not at all. |
+ |
Matches the preceding element once or more times. |
* |
Matches the preceding element zero or more times. |
{n,m} |
Matches the preceding element at least n and at most m times. |
^ |
Matches the start of the text (of a line with the m flag). |
$ |
Matches the end of the text (of a line with the m flag). |
\b |
Matches a word boundary. |
| |
Alternation operator. |
[abc] |
Matches a, or b, or c. |
[a-c] |
Range; matches a, or b, or c. |
[^abc] |
Negation; matches any character except a, b, or c. |
\d |
Matches a digit (Unicode decimal number). |
\s |
Matches a whitespace character. |
\w |
Matches a word character: a Unicode letter, digit, or underscore. |
\p{Greek} |
Matches a character with the given Unicode property. |
(...) |
Capturing group. |
(?<name>...) |
Named capturing group. |
(?:...) |
Non-capturing group. |
(?i) |
Case-insensitive flag. |
The patterns are written as raw string literals, r"...". In a
raw string, the backslash is an ordinary character, so we write
r"\d+" instead of "\\d+".
The is_match method
The is_match method tells whether the regular expression
finds a match anywhere in the input string.
use regex::Regex;
fn main() {
let words = ["Seven", "even", "Maven", "Amen", "eleven"];
let rx = Regex::new(r".even").unwrap();
for word in words {
if rx.is_match(word) {
println!("{word} does match");
} else {
println!("{word} does not match");
}
}
}
In the example, we have five words in an array. We check which words match
the .even regular expression.
let rx = Regex::new(r".even").unwrap();
The Regex::new function compiles the pattern. It returns a
Result, because the pattern may be invalid. The dot stands for
any single character; the rest are ordinary letters.
if rx.is_match(word) {
The method returns true if the pattern matches a part of the
word. The word "even" does not match, because there is no character before
"even". The word "eleven" matches, because "leven" is a part of it.
$ cargo run -q Seven does match even does not match Maven does not match Amen does not match eleven does match
Finding matches
The find method returns the first match, and
find_iter returns an iterator over all non-overlapping matches.
use regex::Regex;
fn main() {
let content = "Foxes are omnivorous mammals belonging to several genera
of the family Canidae. Foxes have a flattened skull, upright triangular ears,
a pointed, slightly upturned snout, and a long bushy tail. Foxes live on every
continent except Antarctica. By far the most common and widespread species of
fox is the red fox.";
let rx = Regex::new(r"(?i)fox(es)?").unwrap();
for m in rx.find_iter(content) {
println!("{} at {}..{}", m.as_str(), m.start(), m.end());
}
println!("{} matches", rx.find_iter(content).count());
if let Some(m) = rx.find(content) {
println!("first match: {:?}", m.range());
}
}
The example finds all occurrences of "fox" or "foxes" in the text, together with their positions.
let rx = Regex::new(r"(?i)fox(es)?").unwrap();
The (?i) flag makes the search case-insensitive. The
(es)? group is optional.
for m in rx.find_iter(content) {
println!("{} at {}..{}", m.as_str(), m.start(), m.end());
}
Each item is a Match. The as_str method returns the
matched text; start and end return the byte offsets
in the input.
println!("{} matches", rx.find_iter(content).count());
The count method of the iterator returns the number of matches.
if let Some(m) = rx.find(content) {
println!("first match: {:?}", m.range());
}
The find method returns an Option<Match>. The
range method returns the offsets as a range, which can be used
to slice the input.
$ cargo run -q Foxes at 0..5 Foxes at 80..85 Foxes at 194..199 fox at 292..295 fox at 307..310 5 matches first match: 0..5
Anchors
Anchors match positions instead of characters. The ^ anchor
matches the start of the text, $ its end, and \b a
boundary between a word and a non-word character.
use regex::Regex;
fn main() {
let sentences = [
"I am looking for Jane.",
"Jane was walking along the river.",
"Kate and Jane are close friends.",
];
let start = Regex::new(r"^Jane").unwrap();
let end = Regex::new(r"Jane\.$").unwrap();
for s in sentences {
println!("{:<36} start: {:<5} end: {}", s, start.is_match(s), end.is_match(s));
}
let word = Regex::new(r"\bis\b").unwrap();
let text = "This island is beautiful.";
let found: Vec<usize> = word.find_iter(text).map(|m| m.start()).collect();
println!("{found:?}");
}
We check whether a sentence starts with "Jane" or ends with "Jane.".
let end = Regex::new(r"Jane\.$").unwrap();
The dot has a special meaning, so it must be escaped to match a literal dot.
let word = Regex::new(r"\bis\b").unwrap();
With word boundaries, the pattern matches only the word "is", not the letters in "This" or "island".
$ cargo run -q I am looking for Jane. start: false end: true Jane was walking along the river. start: true end: false Kate and Jane are close friends. start: false end: false [12]
Alternations
The alternation operator | creates a regular expression with
several choices.
use regex::Regex;
fn main() {
let users = ["Jane", "Thomas", "Robert", "Lucy", "Beky", "John", "Peter", "Andy"];
let rx = Regex::new(r"^(Jane|Beky|Robert)$").unwrap();
let matched: Vec<&str> = users.iter().copied().filter(|u| rx.is_match(u)).collect();
println!("{matched:?}");
let loose = Regex::new(r"Jan|Rob").unwrap();
println!("{}", loose.is_match("Janet"));
println!("{}", rx.is_match("Janet"));
}
We find the names that are equal to Jane, Beky, or Robert.
let rx = Regex::new(r"^(Jane|Beky|Robert)$").unwrap();
The alternation has the lowest precedence, so it is grouped with parentheses. Without the anchors, the pattern would also match strings that merely contain one of the names.
let loose = Regex::new(r"Jan|Rob").unwrap();
println!("{}", loose.is_match("Janet"));
The pattern without anchors matches "Janet", because it contains "Jan".
$ cargo run -q ["Jane", "Robert", "Beky"] true false
Character classes and quantifiers
Character classes match one character from a set; quantifiers specify how many times the preceding element repeats.
use regex::Regex;
fn main() {
let text = "The year 1998 was hot; in 2003 and 2019 too. Call 555-1234 or 555-98765.";
let years = Regex::new(r"\b(19|20)\d{2}\b").unwrap();
let ys: Vec<&str> = years.find_iter(text).map(|m| m.as_str()).collect();
println!("years: {ys:?}");
let phone = Regex::new(r"\b\d{3}-\d{4}\b").unwrap();
let ps: Vec<&str> = phone.find_iter(text).map(|m| m.as_str()).collect();
println!("phones: {ps:?}");
let words = Regex::new(r"\b[a-z]{3,4}\b").unwrap();
let ws: Vec<&str> = words.find_iter(text).map(|m| m.as_str()).collect();
println!("short words: {ws:?}");
let not_word = Regex::new(r"[^\w\s]").unwrap();
let ns: Vec<&str> = not_word.find_iter(text).map(|m| m.as_str()).collect();
println!("punctuation: {ns:?}");
}
We find years, phone numbers, short words, and punctuation characters.
let years = Regex::new(r"\b(19|20)\d{2}\b").unwrap();
A year starts with 19 or 20 and is followed by exactly two digits.
let phone = Regex::new(r"\b\d{3}-\d{4}\b").unwrap();
The pattern matches three digits, a hyphen, and four digits. The number 555-98765 does not match, because the word boundary after four digits is missing.
let words = Regex::new(r"\b[a-z]{3,4}\b").unwrap();
The {3,4} quantifier matches words of three or four lowercase
letters.
let not_word = Regex::new(r"[^\w\s]").unwrap();
A negated class matches any character that is neither a word character nor a whitespace.
$ cargo run -q years: ["1998", "2003", "2019"] phones: ["555-1234"] short words: ["year", "was", "hot", "and", "too"] punctuation: [";", ".", "-", "-", "."]
Greedy and lazy quantifiers
The quantifiers are greedy: they match as much text as possible. Adding a question mark makes them lazy, so that they match as little as possible.
use regex::Regex;
fn main() {
let html = "<p>first</p><p>second</p>";
let greedy = Regex::new(r"<p>.*</p>").unwrap();
let lazy = Regex::new(r"<p>.*?</p>").unwrap();
for m in greedy.find_iter(html) {
println!("greedy: {}", m.as_str());
}
for m in lazy.find_iter(html) {
println!("lazy: {}", m.as_str());
}
}
We match HTML paragraphs with a greedy and a lazy quantifier.
let greedy = Regex::new(r"<p>.*</p>").unwrap(); let lazy = Regex::new(r"<p>.*?</p>").unwrap();
The greedy .* runs to the last </p>, so it
matches both paragraphs at once. The lazy .*? stops at the first
</p>. Regular expressions are fine for simple fragments
like this; for real HTML documents, use an HTML parser.
$ cargo run -q greedy: <p>first</p><p>second</p> lazy: <p>first</p> lazy: <p>second</p>
Capturing groups
Round brackets create capturing groups. The captures method
returns the text matched by the whole pattern and by each group.
use regex::Regex;
fn main() {
let sites = ["example.com", "zetcode.com", "freebsd.org", "rust-lang.org", "localhost"];
let rx = Regex::new(r"^([\w-]+)\.(\w+)$").unwrap();
for site in sites {
match rx.captures(site) {
Some(caps) => println!("{:<14} name: {:<10} domain: {}", &caps[0], &caps[1], &caps[2]),
None => println!("{site:<14} no match"),
}
}
}
In the example, we divide domain names into two parts.
let rx = Regex::new(r"^([\w-]+)\.(\w+)$").unwrap();
The pattern has two groups: the name, which may contain hyphens, and the top-level domain.
match rx.captures(site) {
Some(caps) => println!("{:<14} name: {:<10} domain: {}", &caps[0], &caps[1], &caps[2]),
None => println!("{site:<14} no match"),
}
The captures method returns None if the pattern does
not match. The group at the index 0 is the whole match; the groups are
numbered from 1 by their opening parentheses. Indexing panics if the group did
not participate in the match.
$ cargo run -q example.com name: example domain: com zetcode.com name: zetcode domain: com freebsd.org name: freebsd domain: org rust-lang.org name: rust-lang domain: org localhost no match
Named groups
Groups can have names, which make the patterns easier to read. The
captures_iter method returns the captures for every match.
use regex::Regex;
fn main() {
let text = "Released on 2024-03-15, patched on 2024-11-02 and 2025-01-20.";
let rx = Regex::new(r"(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})").unwrap();
for caps in rx.captures_iter(text) {
let year: u32 = caps["year"].parse().unwrap();
println!("day {} month {} year {year}", &caps["day"], &caps["month"]);
}
for (_, [y, m, d]) in rx.captures_iter(text).map(|c| c.extract()) {
println!("{d}.{m}.{y}");
}
let caps = rx.captures("no date here");
println!("{caps:?}");
}
We extract dates from a text.
let rx = Regex::new(r"(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})").unwrap();
The (?<name>...) syntax defines named groups. The
(?P<name>...) syntax known from Python is supported too.
let year: u32 = caps["year"].parse().unwrap();
Named groups are accessed by indexing with the group name. The captured text is a string slice; we parse it into a number.
for (_, [y, m, d]) in rx.captures_iter(text).map(|c| c.extract()) {
println!("{d}.{m}.{y}");
}
The extract method returns the whole match and an array of the
groups. The size of the array is checked when the program runs; it must be
equal to the number of groups, and all groups must participate in the match.
$ cargo run -q day 15 month 03 year 2024 day 02 month 11 year 2024 day 20 month 01 year 2025 15.03.2024 02.11.2024 20.01.2025 None
Optional groups
An optional group may not participate in a match. The get method
returns an Option<Match> for a group, so it handles this
case without panicking.
use regex::Regex;
fn main() {
let rx = Regex::new(r"(\d+)(?:\.(\d+))?\s*(kg|g)").unwrap();
for s in ["12 kg", "3.5kg", "250 g", "7 lb"] {
let Some(caps) = rx.captures(s) else {
println!("{s}: no match");
continue;
};
let whole = &caps[1];
let frac = caps.get(2).map_or("0", |m| m.as_str());
let unit = &caps[3];
println!("{s}: whole {whole}, fraction {frac}, unit {unit}");
}
}
We parse weights with an optional fractional part.
let rx = Regex::new(r"(\d+)(?:\.(\d+))?\s*(kg|g)").unwrap();
The (?:...) group is non-capturing; it only groups the dot with
the digits, so that the ? applies to both. The inner group with
the digits is the second capturing group.
let frac = caps.get(2).map_or("0", |m| m.as_str());
For "12 kg", the second group did not participate in the match;
get returns None and we use a default value.
$ cargo run -q 12 kg: whole 12, fraction 0, unit kg 3.5kg: whole 3, fraction 5, unit kg 250 g: whole 250, fraction 0, unit g 7 lb: no match
Replacing text
The replace method replaces the first match and
replace_all replaces all matches. The replacement string can
refer to the groups.
use regex::Regex;
fn main() {
let text = "My name is John Doe. John lives in London.";
let rx = Regex::new(r"John").unwrap();
println!("{}", rx.replace(text, "Jane"));
println!("{}", rx.replace_all(text, "Jane"));
let names = Regex::new(r"(?<first>\w+) (?<last>Doe)").unwrap();
println!("{}", names.replace(text, "$last, $first"));
let spaces = Regex::new(r"\s+").unwrap();
println!("{}", spaces.replace_all("an old \t falcon", " "));
let ids = Regex::new(r"(\d+)").unwrap();
println!("{}", ids.replace_all("item 7, item 12", "${1}a"));
}
The example shows several kinds of replacements.
println!("{}", rx.replace(text, "Jane"));
println!("{}", rx.replace_all(text, "Jane"));
The methods return a Cow<str>. If there is no match, the
input is returned without copying.
let names = Regex::new(r"(?<first>\w+) (?<last>Doe)").unwrap();
println!("{}", names.replace(text, "$last, $first"));
In the replacement string, $name or $1 refers to a
group. We swap the first and the last name.
println!("{}", ids.replace_all("item 7, item 12", "${1}a"));
When a group reference is followed by a letter or a digit, it must be
enclosed in braces. The $1a would refer to a group named
1a. A literal dollar sign is written as $$.
$ cargo run -q My name is Jane Doe. John lives in London. My name is Jane Doe. Jane lives in London. My name is Doe, John. John lives in London. an old falcon item 7a, item 12a
Replacing with a closure
The replacement can be computed by a closure, which receives the captures of each match.
use regex::{Captures, Regex};
fn main() {
let text = "3 apples cost 120 cents, 12 plums cost 300 cents.";
let rx = Regex::new(r"(\d+) cents").unwrap();
let res = rx.replace_all(text, |caps: &Captures| {
let cents: u32 = caps[1].parse().unwrap();
format!("${}.{:02}", cents / 100, cents % 100)
});
println!("{res}");
let words = Regex::new(r"\b\w").unwrap();
let title = words.replace_all("the old man and the sea", |caps: &Captures| caps[0].to_uppercase());
println!("{title}");
}
We convert prices in cents into dollars and capitalize the words of a title.
let res = rx.replace_all(text, |caps: &Captures| {
let cents: u32 = caps[1].parse().unwrap();
format!("${}.{:02}", cents / 100, cents % 100)
});
The closure returns the replacement as a String. Unlike in a
replacement string, the dollar sign in the closure output is a literal
character.
let words = Regex::new(r"\b\w").unwrap();
The pattern matches the first character of every word; the closure turns it into uppercase.
$ cargo run -q 3 apples cost $1.20, 12 plums cost $3.00. The Old Man And The Sea
Splitting text
The split method splits the text at the matches of the pattern;
splitn limits the number of parts.
use regex::Regex;
fn main() {
let text = "sky, blue;cloud ; rain,,snow";
let rx = Regex::new(r"\s*[,;]\s*").unwrap();
let parts: Vec<&str> = rx.split(text).collect();
println!("{parts:?}");
let parts: Vec<&str> = rx.split(text).filter(|s| !s.is_empty()).collect();
println!("{parts:?}");
let parts: Vec<&str> = rx.splitn(text, 2).collect();
println!("{parts:?}");
}
We split a list whose items are separated with commas or semicolons and an arbitrary amount of whitespace.
let rx = Regex::new(r"\s*[,;]\s*").unwrap();
The separator is a comma or a semicolon with optional whitespace around it.
let parts: Vec<&str> = rx.split(text).filter(|s| !s.is_empty()).collect();
Two separators in a row produce an empty string, which we filter out.
let parts: Vec<&str> = rx.splitn(text, 2).collect();
With splitn, the last part contains the rest of the text.
$ cargo run -q ["sky", "blue", "cloud", "rain", "", "snow"] ["sky", "blue", "cloud", "rain", "snow"] ["sky", "blue;cloud ; rain,,snow"]
Flags
Flags change how the pattern is matched. They are written inside the pattern
with the (?flags) syntax or set with RegexBuilder.
use regex::{Regex, RegexBuilder};
fn main() {
let text = "Rust is fast.\nrust is safe.\nRUST is fun.";
let rx = Regex::new(r"(?im)^rust").unwrap();
println!("{}", rx.find_iter(text).count());
let rx = Regex::new(r"^rust").unwrap();
println!("{}", rx.find_iter(text).count());
let rx = RegexBuilder::new(r"^rust")
.case_insensitive(true)
.multi_line(true)
.build()
.unwrap();
println!("{}", rx.find_iter(text).count());
let rx = Regex::new(r"(?s)fast.*safe").unwrap();
println!("{}", rx.is_match(text));
let date = Regex::new(
r"(?x)
(?<y>\d{4}) # the year
-
(?<m>\d{2}) # the month
",
)
.unwrap();
println!("{}", &date.captures("in 2026-10").unwrap()["m"]);
}
We count the lines that start with "rust" in any case.
let rx = Regex::new(r"(?im)^rust").unwrap();
The i flag enables case-insensitive matching; the
m flag makes ^ and $ match at the start
and end of each line. Without the flags, there is no match, because the text
starts with "Rust".
let rx = RegexBuilder::new(r"^rust")
.case_insensitive(true)
.multi_line(true)
.build()
.unwrap();
The builder sets the same flags with methods.
let rx = Regex::new(r"(?s)fast.*safe").unwrap();
With the s flag, the dot also matches the newline, so the
pattern can match across lines.
let date = Regex::new(
r"(?x)
(?<y>\d{4}) # the year
-
(?<m>\d{2}) # the month
",
)
The x flag enables the verbose mode: whitespace in the pattern is
ignored and # starts a comment. Complex patterns become easier
to read.
$ cargo run -q 3 0 3 true 10
Validating input
To check that a whole string has a given form, the pattern is anchored at both ends.
use regex::Regex;
fn main() {
let rx = Regex::new(r"^[\w.+-]+@[\w-]+(\.[\w-]+)*\.[a-z]{2,}$").unwrap();
let emails = [
"luke@gmail.com",
"andy@yahoocom",
"34234sdfa#2345",
"f344@gmail.com",
"jane.doe+news@mail.example.org",
"@example.com",
];
for email in emails {
let status = if rx.is_match(email) { "valid" } else { "invalid" };
println!("{email:<32} {status}");
}
}
We check a list of email addresses.
let rx = Regex::new(r"^[\w.+-]+@[\w-]+(\.[\w-]+)*\.[a-z]{2,}$").unwrap();
The local part consists of word characters, dots, pluses, and hyphens. The domain has one or more parts separated by dots and ends with a top-level domain of at least two letters. The pattern is a simplification; the full syntax of email addresses is much more complex, and the only reliable check is to send an email.
$ cargo run -q luke@gmail.com valid andy@yahoocom invalid 34234sdfa#2345 invalid f344@gmail.com valid jane.doe+news@mail.example.org valid @example.com invalid
Unicode
The classes \w, \d, and \s are
Unicode-aware. Unicode properties are matched with \p{...}.
use regex::Regex;
fn main() {
let text = "Žltý kôň, 東京 and Αθήνα; café ŁÓDŹ";
let words = Regex::new(r"\w+").unwrap();
let ws: Vec<&str> = words.find_iter(text).map(|m| m.as_str()).collect();
println!("{ws:?}");
let ascii = Regex::new(r"(?-u:\w)+").unwrap();
let ascii_ws: Vec<&str> = ascii.find_iter(text).map(|m| m.as_str()).collect();
println!("{ascii_ws:?}");
let greek = Regex::new(r"\p{Greek}+").unwrap();
println!("{:?}", greek.find(text).map(|m| m.as_str()));
let han = Regex::new(r"\p{Han}+").unwrap();
println!("{:?}", han.find(text).map(|m| m.as_str()));
let upper = Regex::new(r"\b\p{Lu}+\b").unwrap();
println!("{:?}", upper.find(text).map(|m| m.as_str()));
}
We find words in a text that contains Slovak, Japanese, Greek, French, and Polish words.
let words = Regex::new(r"\w+").unwrap();
The \w class matches letters of all alphabets.
let ascii = Regex::new(r"(?-u:\w)+").unwrap();
The (?-u:...) group disables the Unicode mode; the class then
matches only [0-9A-Za-z_] and splits the words with accented
letters.
let greek = Regex::new(r"\p{Greek}+").unwrap();
let han = Regex::new(r"\p{Han}+").unwrap();
let upper = Regex::new(r"\b\p{Lu}+\b").unwrap();
The \p{Greek} and \p{Han} classes match scripts;
\p{Lu} matches uppercase letters.
$ cargo run -q
["Žltý", "kôň", "東京", "and", "Αθήνα", "café", "ŁÓDŹ"]
["lt", "k", "and", "caf", "D"]
Some("Αθήνα")
Some("東京")
Some("ŁÓDŹ")
Regex sets
A RegexSet matches several patterns in a single pass over the
input and reports which of them matched.
use regex::RegexSet;
fn main() {
let set = RegexSet::new([
r"^\d+$",
r"^[a-z]+$",
r"^[A-Z]",
r"\s",
])
.unwrap();
let names = ["digits", "lowercase", "capitalized", "has space"];
for s in ["2026", "falcon", "Rust", "an old Falcon"] {
let hits: Vec<&str> = set.matches(s).into_iter().map(|i| names[i]).collect();
println!("{s:<14} {hits:?}");
}
}
We classify strings with four patterns.
let hits: Vec<&str> = set.matches(s).into_iter().map(|i| names[i]).collect();
The matches method returns the indexes of the patterns that
matched. A set does not report the positions of the matches; for these, we
use the individual regexes.
$ cargo run -q 2026 ["digits"] falcon ["lowercase"] Rust ["capitalized"] an old Falcon ["has space"]
Compiling a regex once
Compiling a regular expression is much slower than using it. A regex used in
a function that is called many times should be compiled only once. A
LazyLock static compiles it on first use.
use regex::Regex;
use std::sync::LazyLock;
static WORD: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\b[A-Z]\w*").unwrap());
fn capitalized(text: &str) -> Vec<&str> {
WORD.find_iter(text).map(|m| m.as_str()).collect()
}
fn main() {
let lines = [
"Alice met Bob in Paris.",
"the weather was fine.",
"They visited the Louvre and Notre-Dame.",
];
for line in lines {
println!("{:?}", capitalized(line));
}
}
We find capitalized words in several lines.
static WORD: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\b[A-Z]\w*").unwrap());
The LazyLock type from the standard library runs the closure the
first time the static is accessed. The Regex can be shared
between threads, so it can be used from a static.
fn capitalized(text: &str) -> Vec<&str> {
WORD.find_iter(text).map(|m| m.as_str()).collect()
}
The function returns slices of the input text. "Notre-Dame" is split into two matches, because the hyphen is not a word character.
$ cargo run -q ["Alice", "Bob", "Paris"] [] ["They", "Louvre", "Notre", "Dame"]
Invalid patterns
An invalid pattern is reported as an error by Regex::new. When a
pattern comes from the user, the error must be handled.
use regex::Regex;
fn main() {
for pattern in [r"\d+", r"(\d+", r"[z-a]", r"\p{Foo}"] {
match Regex::new(pattern) {
Ok(rx) => println!("ok: {rx}"),
Err(e) => println!("{e}\n"),
}
}
}
We try to compile one valid and three invalid patterns.
match Regex::new(pattern) {
Ok(rx) => println!("ok: {rx}"),
Err(e) => println!("{e}\n"),
}
The error message shows the pattern and marks the position of the problem. A
regex printed with {} displays its pattern.
$ cargo run -q
ok: \d+
regex parse error:
(\d+
^
error: unclosed group
regex parse error:
[z-a]
^^^
error: invalid character class range, the start must be <= the end
regex parse error:
\p{Foo}
^^^^^^^
error: Unicode property not found
Source
In this article we have used regular expressions in Rust.
Author
List all Rust tutorials.