Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
62: Working with Regular Expressions in Rust
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
regexcrate 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_allmethod provided by theRegexstruct 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]."
There are no comments for now.