Loading page…
Rust walkthroughs
Loading page…
regex::Regex::is_match vs regex::Regex::find for simple match checks?is_match returns a boolean indicating whether the pattern matches anywhere in the input, while find returns the byte offsets of the first match. The key performance difference is that is_match can short-circuit after finding any match—it doesn't need to compute match boundaries—while find must locate and return the exact position. For simple existence checks, is_match is faster because it does less work. However, find is only marginally slower in most cases because the regex engine optimizes both paths similarly, and the overhead of position calculation is often negligible compared to the matching work itself. The choice should prioritize API semantics: use is_match when you only need to know if a match exists, and find when you need where it occurs.
use regex::Regex;
fn main() {
let pattern = Regex::new(r"\d+").unwrap();
let text = "The answer is 42";
// is_match returns bool - does the pattern match?
if pattern.is_match(text) {
println!("Found a number!");
}
}is_match returns true if the pattern matches anywhere in the text.
use regex::Regex;
fn main() {
let pattern = Regex::new(r"\d+").unwrap();
let text = "The answer is 42";
// find returns Option<Match> - where is the match?
if let Some(m) = pattern.find(text) {
println!("Found '{}' at byte positions {}-{}", m.as_str(), m.start(), m.end());
}
}find returns the position and content of the first match.
use regex::Regex;
fn main() {
let pattern = Regex::new(r"\d+").unwrap();
let text = "Numbers: 1, 2, 3";
// is_match: "Is there a match?"
let exists = pattern.is_match(text);
println!("Match exists: {}", exists);
// find: "Where is the first match?"
let first = pattern.find(text);
println!("First match: {:?}", first.map(|m| m.as_str()));
// Both found something, but find gives you position
}Use is_match for existence checks; find when position matters.
use regex::Regex;
fn main() {
// Pattern that could match late in a long string
let pattern = Regex::new(r"target_word").unwrap();
let text = "word word word word word word word word target_word";
// is_match can return true immediately upon finding match
let matched = pattern.is_match(text);
// find must track the position of the match
let found = pattern.find(text);
// Both found the match, but is_match doesn't track position
}is_match avoids the work of tracking and returning match boundaries.
use regex::Regex;
fn main() {
let pattern = Regex::new(r"[a-z]+").unwrap();
let text = "hello world";
// find must return exact byte offsets
let m = pattern.find(text).unwrap();
println!("Match at {}..{}: '{}'", m.start(), m.end(), m.as_str());
// is_match just needs to confirm existence
let matches = pattern.is_match(text);
println!("Has match: {}", matches);
}find computes start and end positions; is_match skips this work.
use regex::Regex;
fn main() {
let pattern = Regex::new(r"\d{4}-\d{2}-\d{2}").unwrap(); // Date pattern
let text = "The date is 2024-01-15 for the event.";
// These are roughly equivalent in performance for simple patterns
// The regex engine optimizes both similarly
let start = std::time::Instant::now();
for _ in 0..100_000 {
let _ = pattern.is_match(text);
}
let is_match_time = start.elapsed();
let start = std::time::Instant::now();
for _ in 0..100_000 {
let _ = pattern.find(text);
}
let find_time = start.elapsed();
println!("is_match: {:?}", is_match_time);
println!("find: {:?}", find_time);
// is_match is typically faster, but difference is small
}For simple patterns, the difference is often negligible.
use regex::Regex;
fn main() {
let pattern = Regex::new(r"\d+").unwrap();
let text = "12345 is the first number";
// Match is at the beginning - both methods are fast
let start = std::time::Instant::now();
for _ in 0..100_000 {
let _ = pattern.is_match(text);
}
println!("is_match (early): {:?}", start.elapsed());
let start = std::time::Instant::now();
for _ in 0..100_000 {
let _ = pattern.find(text);
}
println!("find (early): {:?}", start.elapsed());
// Both are fast when match is found early
}Early matches minimize work for both methods.
use regex::Regex;
fn main() {
let pattern = Regex::new(r"[A-Z]{10}").unwrap(); // 10 uppercase letters
let text = "lowercase words only in this string";
// No match - both must scan entire string
let start = std::time::Instant::now();
for _ in 0..100_000 {
let _ = pattern.is_match(text);
}
println!("is_match (no match): {:?}", start.elapsed());
let start = std::time::Instant::now();
for _ in 0..100_000 {
let _ = pattern.find(text);
}
println!("find (no match): {:?}", start.elapsed());
// Both are equivalent when no match exists
}When there's no match, both methods scan the entire input.
use regex::Regex;
fn main() {
// Anchored pattern - must match at specific position
let anchored = Regex::new(r"^\d+").unwrap(); // Start of string
let text = "12345 rest of string";
// Anchored patterns are very fast for both methods
// is_match returns quickly after checking start
// find returns quickly if match at start
let start = std::time::Instant::now();
for _ in 0..100_000 {
let _ = anchored.is_match(text);
}
println!("is_match (anchored): {:?}", start.elapsed());
let start = std::time::Instant::now();
for _ in 0..100_000 {
let _ = anchored.find(text);
}
println!("find (anchored): {:?}", start.elapsed());
}Anchored patterns benefit both methods equally.
use regex::Regex;
fn main() {
// Complex pattern with alternation and quantifiers
let pattern = Regex::new(r"(foo|bar|baz)+\d{3,5}").unwrap();
let text = "foobarbaz12345 and more";
// is_match short-circuits after finding any match
let start = std::time::Instant::now();
for _ in 0..100_000 {
let _ = pattern.is_match(text);
}
println!("is_match (complex): {:?}", start.elapsed());
// find needs to track where the match ends
let start = std::time::Instant::now();
for _ in 0..100_000 {
let _ = pattern.find(text);
}
println!("find (complex): {:?}", start.elapsed());
// is_match shows clearer advantage for complex patterns
}Complex patterns show larger is_match advantage.
use regex::Regex;
fn main() {
let pattern = Regex::new(r"(\d+)-(\d+)-(\d+)").unwrap();
let text = "Date: 2024-01-15";
// is_match: "Is there a match?"
let matched = pattern.is_match(text);
// find: "Where is the match?"
let location = pattern.find(text);
// captures: "What are the groups?"
let caps = pattern.captures(text);
// Performance: is_match > find > captures
// (fastest to slowest)
println!("Matched: {}", matched);
if let Some(m) = location {
println!("Location: {}-{}", m.start(), m.end());
}
if let Some(c) = caps {
println!("Groups: {:?}", c.iter().collect::<Vec<_>>());
}
}is_match < find < captures in terms of work done.
use regex::Regex;
fn main() {
let pattern = Regex::new(r"\d+").unwrap();
let text = "1, 2, 3, 4, 5";
// For counting matches, is_match just tells you if >= 1
let has_numbers = pattern.is_match(text);
// find gives you the first match
let first = pattern.find(text);
// find_iter gives you all matches
let count = pattern.find_iter(text).count();
println!("Has numbers: {}", has_numbers);
println!("First: {:?}", first.map(|m| m.as_str()));
println!("Count: {}", count);
}Use find_iter when you need all match positions.
use regex::Regex;
fn validate_input(input: &str) -> bool {
let email_pattern = Regex::new(r"^[^@]+@[^@]+\.[^@]+$").unwrap();
// We only care if the pattern matches
// No need for position information
email_pattern.is_match(input)
}
fn contains_forbidden_word(text: &str) -> bool {
let forbidden = Regex::new(r"\b(spam|scam|phishing)\b").unwrap();
// Just checking existence - position doesn't matter
forbidden.is_match(text)
}
fn main() {
println!("Valid email: {}", validate_input("test@example.com"));
println!("Contains forbidden: {}", contains_forbidden_word("Hello spam world"));
}Use is_match for validation and existence checks.
use regex::Regex;
fn extract_first_number(text: &str) -> Option<String> {
let number_pattern = Regex::new(r"\d+").unwrap();
// We need the position and content of the match
number_pattern.find(text).map(|m| m.as_str().to_string())
}
fn highlight_match(text: &str, pattern: &Regex) -> String {
match pattern.find(text) {
Some(m) => {
let before = &text[..m.start()];
let matched = m.as_str();
let after = &text[m.end()..];
format!("{}[{}]{}", before, matched, after)
}
None => text.to_string(),
}
}
fn main() {
println!("Number: {:?}", extract_first_number("The value is 42"));
let pattern = Regex::new(r"\d+").unwrap();
println!("Highlighted: {}", highlight_match("Age: 25", &pattern));
}Use find when you need match position or content.
use regex::Regex;
fn main() {
let pattern = Regex::new(r"error|warning|fatal").unwrap();
let text = "This line has an error in it and continues";
// is_match can return true immediately after finding "error"
// It doesn't scan the rest of the string
// find also stops at first match, but must track position
// Both short-circuit at the first match
// But is_match has less bookkeeping
}Both methods short-circuit on first match; is_match has less overhead.
use regex::Regex;
fn main() {
// Unicode pattern
let pattern = Regex::new(r"\p{L}+").unwrap(); // Unicode letters
let text = "Hello 世界";
// Both handle Unicode correctly
let matches = pattern.is_match(text);
let found = pattern.find(text);
println!("Matches: {}", matches);
if let Some(m) = found {
println!("Found '{}' at {}-{}", m.as_str(), m.start(), m.end());
}
// find returns byte indices, not character indices
}Both methods handle Unicode; find returns byte positions.
use regex::Regex;
fn main() {
let pattern = Regex::new(r"needle").unwrap();
// Large text where match is early
let early_match_text = "needle " + &" ".repeat(1_000_000);
// Large text where match is late
let late_match_text = " ".repeat(1_000_000) + "needle";
// Large text with no match
let no_match_text = " ".repeat(1_000_000);
// is_match advantages:
// - Early match: both fast, is_match slightly faster
// - Late match: both slow, is_match slightly faster
// - No match: both slow, similar performance
// The key insight: is_match advantage scales with match position
// Earlier match = earlier short-circuit = more advantage
}Performance difference scales with match position.
use regex::Regex;
// Good: is_match for validation
fn is_valid_id(id: &str) -> bool {
let pattern = Regex::new(r"^[A-Z]{2}\d{6}$").unwrap();
pattern.is_match(id)
}
// Good: find for extraction
fn get_first_code(text: &str) -> Option<&str> {
let pattern = Regex::new(r"CODE-\d+").unwrap();
pattern.find(text).map(|m| m.as_str())
}
// Avoid: using find when is_match suffices
fn check_pattern_bad(text: &str) -> bool {
let pattern = Regex::new(r"\d+").unwrap();
pattern.find(text).is_some() // Slower than is_match
}
// Good: using is_match directly
fn check_pattern_good(text: &str) -> bool {
let pattern = Regex::new(r"\d+").unwrap();
pattern.is_match(text) // Faster, clearer intent
}Use the method that matches your intent.
| Method | Returns | Use Case | Performance |
|--------|---------|----------|-------------|
| is_match | bool | Existence check | Fastest |
| find | Option<Match> | Position needed | Slightly slower |
| find_iter | FindIter | All matches | Slowest (scans all) |
| captures | Option<Captures> | Groups needed | Slowest |
is_match advantages:
find advantages:
is_match in most casesWhen to choose is_match:
When to choose find:
Performance reality: The difference between is_match and find is typically small (often <10% for real-world patterns). The regex engine optimizes both similarly, and the main cost is the regex matching itself, not the small overhead of returning position. Choose based on API semantics: if you need position, use find; if you only need existence, use is_match. The performance difference is rarely the deciding factor in practice.