Skip to Content
Course content

62: Working with Regular Expressions in Rust

Click on the "Edit" button in the top corner of the screen to edit your slide content.

First thing's first: if you look through the standard library, you're going to notice something frustrating—there is no regex module. Rust keeps std lean to keep compile times down and the binary size small. To do anything with regular expressions, you need the regex crate. Add regex = "1" to your Cargo.toml, and you're ready to go.

Why do I have to handle a Result just to create a regex?

In many languages, you just write a string and the engine handles it at runtime. In Rust, Regex::new() returns a Result. This is because your regex pattern is essentially a mini-program that needs to be compiled. If you have a typo in your syntax—like an unclosed parenthesis—the "compilation" of that pattern fails.

I usually just unwrap() this during prototyping, but in production, you'll want to handle it. Here is a basic example of how I'd check if a string looks like a standard ISO 8601 date (YYYY-MM-DD):

use regex::Regex;

fn is_valid_date(date: &str) -> bool {
    // I'm using unwrap here because I know this pattern is hard-coded and correct.
    // If the pattern came from a user, I'd return a Result instead.
    let re = Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();
    re.is_match(date)
}

Notice the r"..."? That's a raw string literal. It tells Rust not to treat backslashes as escape characters, which saves you from having to write "\\\\d{4}" just to match a digit. Always use raw strings for regex.

How do I actually pull specific data out of a match?

Checking if a string matches is one thing, but usually, you want to extract pieces of it. For that, you use "capture groups"—the parentheses in your regex. Instead of is_match, you'll use captures.

Let's say you're parsing a log file where entries look like [INFO] User 42 logged in. You want the log level and the user ID separately:

use regex::Regex;

fn main() {
    let log_entry = "[INFO] User 42 logged in";
    let re = Regex::new(r"\[(?P<level>\w+)\] User (?P<id>\d+)").unwrap();

    if let Some(caps) = re.captures(log_entry) {
        // You can access groups by index...
        println!("Level: {}, ID: {}", &caps[1], &caps[2]);
        
        // ...or by name, which I highly recommend for readability.
        println!("Level: {}, ID: {}", &caps["level"], &caps["id"]);
    }
}

I prefer named captures (the ?P<name> syntax) because six months from now, you won't remember if the User ID was group 2 or group 3. It makes your code much more maintainable.

Is it slow to create a new Regex object inside a loop?

Yes. Extremely. Every time you call Regex::new(), Rust has to parse the string, compile it into a finite automaton, and optimize it. If you do this inside a for loop processing a million lines of a file, your program will crawl.

The solution is to compile the regex once and reuse it. Since Regex is thread-safe (it implements Sync), you can store it in a global static variable. In modern Rust, the cleanest way to do this is with std::sync::OnceLock:

use regex::Regex;
use std::sync::OnceLock;

fn parse_line(line: &str) {
    // This is compiled exactly once for the lifetime of the program.
    static RE: OnceLock<Regex> = OnceLock::new();
    let re = RE.get_or_init(|| {
        Regex::new(r"(\d{1,3}\.){3}\d{1,3}").unwrap()
    });

    if re.is_match(line) {
        println!("Found an IP address!");
    }
}

By moving the compilation out of the hot path, you're shifting that cost to the first time the function is called, making every subsequent call incredibly fast.




📋 Practical Task

Exercise: Building a Log-Entry Sanitizer

You've been asked to build a tool that scrubs sensitive information from server logs before they are sent to a third-party analytics provider. Your goal is to find all IPv4 addresses in a block of text and replace them with the string [REDACTED].

Requirements:

  • Add the regex crate to your project.
  • Create a function sanitize_logs(input: &str) -> String.
  • Use a regular expression that identifies IPv4 addresses (four sets of 1-3 digits separated by dots).
  • Use the replace_all method provided by the Regex struct to swap the IPs for [REDACTED].

Test Case:
Input: "Error at 192.168.1.1: Connection timed out. Retry from 10.0.0.254."
Expected Output: "Error at [REDACTED]: Connection timed out. Retry from [REDACTED]."

Rating
0 0

There are no comments for now.

to be the first to leave a comment.