Regular Expressions in Rust with Examples: In Practice
\n\nRegular expressions (regex) are a powerful tool for working with text. In Rust, despite its strong typing and focus on safety, working with regular expressions is implemented efficiently and conveniently thanks to the regex crate. In this article, we will break down how to use regular expressions in Rust in practice: from installation to complex string processing scenarios.
We will cover the main operations: searching, extraction, replacement, and validation. All code examples are written in Rust and ready to use.
\n\nInstalling and Adding the regex Crate
\n\nTo work with regular expressions in Rust, a third-party but very popular crate called regex is used. It is not part of the standard library, so it needs to be added to the project.
Add the dependency to the Cargo.toml file:
[dependencies]\nregex = "1"\n\nAfter that, you can import the main types in your code:
\n\nuse regex::Regex;\n\nThe regex crate compiles the regular expression into a finite state machine, which ensures high performance. Pattern compilation errors are caught at runtime (via Result), making the code reliable.
Basics: Searching for Matches in a String
\n\nThe simplest way to check if a string matches a pattern is to use the is_match method. Let's look at an example of checking for an email address in text.
use regex::Regex;\n\nfn main() {\n let re = Regex::new(r"\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b").unwrap();\n let text = "Contact us at support@example.com.";\n\n if re.is_match(text) {\n println!("Email found!");\n } else {\n println!("Email not found.");\n }\n}\n\nThe Regex::new method takes a pattern string and returns a Result. unwrap() is used here for simplicity, but in production code, errors should be handled properly.
To find all matches, use the find_iter method, which returns an iterator over Match objects:
use regex::Regex;\n\nfn main() {\n let re = Regex::new(r"\\d+").unwrap();\n let text = "Prices: 100, 250 and 500 rubles.";\n\n for mat in re.find_iter(text) {\n println!("Found number: {}", mat.as_str());\n }\n}\n\nThis code will output:
\n\nFound number: 100\nFound number: 250\nFound number: 500\n\nData Extraction Using Capturing Groups
\n\nCapturing groups allow you to extract not just the entire match, but its individual parts. For example, parsing a date in the "dd-mm-yyyy" format.
\n\nuse regex::Regex;\n\nfn main() {\n let re = Regex::new(r"(\\d{2})-(\\d{2})-(\\d{4})").unwrap();\n let text = "Today is 15-03-2024.";\n\n if let Some(caps) = re.captures(text) {\n println!("Day: {}", &caps[1]);\n println!("Month: {}", &caps[2]);\n println!("Year: {}", &caps[3]);\n }\n}\n\nGroup indexing starts at 1 (group 0 is the entire match). The captures method returns Option<Captures>. If a match is found, we can access the groups by index.
To extract all occurrences with groups, use captures_iter:
use regex::Regex;\n\nfn main() {\n let re = Regex::new(r"(\\w+)@(\\w+)\\.(\\w+)").unwrap();\n let text = "emails: user@host.com, admin@site.org";\n\n for caps in re.captures_iter(text) {\n println!("Login: {}, domain: {}, zone: {}", &caps[1], &caps[2], &caps[3]);\n }\n}\n\nThis approach is indispensable when parsing logs, configuration files, or extracting structured data from unstructured text.
\n\nText Replacement by Pattern
\n\nThe regex crate provides methods for text replacement. The replace method replaces the first match, and replace_all replaces all matches.
use regex::Regex;\n\nfn main() {\n let re = Regex::new(r"\\bRust\\b").unwrap();\n let text = "Rust is great. I love Rust!";\n let result = re.replace_all(text, "Python");\n println!("{}", result);\n}\n\nThis code will output:
\n\nPython is great. I love Python!\n\nYou can also use backreferences in the replacement string to refer to captured groups:
\n\nuse regex::Regex;\n\nfn main() {\n let re = Regex::new(r"(\\w+)@(\\w+)\\.(\\w+)").unwrap();\n let text = "user@host.com";\n let result = re.replace(text, "$1 at $2 dot $3");\n println!("{}", result);\n}\n\nThis will output:
\n\nuser at host dot com\n\nString Validation
\n\nRegular expressions are often used for input validation. For example, checking the correctness of a phone number or password strength.
\n\nuse regex::Regex;\n\nfn validate_phone(phone: &str) -> bool {\n let re = Regex::new(r"^\\+?[1-9]\\d{1,14}$").unwrap();\n re.is_match(phone)\n}\n\nfn main() {\n let phone1 = "+71234567890";\n let phone2 = "12345";\n println!("{} is valid: {}", phone1, validate_phone(phone1));\n println!("{} is valid: {}", phone2, validate_phone(phone2));\n}\n\nThis example checks if the string matches the international phone number format (E.164).
\n\nPerformance and Safety Tips
\n\nWhen working with regular expressions in Rust, it's important to remember a few key points:
\n\n- \n
- Compile once, use many times: Creating a
Regexobject is a relatively expensive operation. If you use the same pattern repeatedly, create the object once and reuse it. \n - Avoid catastrophic backtracking: Although the
regexcrate uses a finite automaton and is not prone to catastrophic backtracking like some other regex engines, complex patterns can still be slow. \n - Use raw string literals: Using
r"..."avoids the need to escape backslashes, making patterns more readable. \n - Handle errors: Always handle the
Resultreturned byRegex::new, especially if the pattern comes from user input. \n
Conclusion
\n\nRegular expressions in Rust are a powerful and efficient tool for text processing. The regex crate provides a rich API for searching, extracting, replacing, and validating strings. Despite the need to add an external dependency, the performance and safety of this crate make it the standard choice for working with regular expressions in the Rust ecosystem.
We've covered the basic operations: checking for matches, extracting data using groups, replacing text, and validating input. These examples will help you get started with using regular expressions in your Rust projects.