#Parsing Text

From simple string splitting to full grammar parsers, Rust has tools for every level of text parsing complexity.

#String Methods

For simple cases, the standard library is often sufficient:

let csv_line = "Alice,95,A";
let fields: Vec<&str> = csv_line.split(',').collect();
assert_eq!(fields, vec!["Alice", "95", "A"]);

let trimmed = "  hello world  ".trim();
assert_eq!(trimmed, "hello world");

let replaced = "foo-bar-baz".replace('-', "_");
assert_eq!(replaced, "foo_bar_baz");

#Parsing Numbers

Convert strings to numeric types with parse:

let port: u16 = "8080".parse().expect("valid port number");
let temperature: f64 = "-12.5".parse().expect("valid temperature");

// Handling parse errors gracefully
fn parse_optional_int(input: &str) -> Option<i64> {
    input.trim().parse().ok()
}

#Regular Expressions

Use the regex crate for pattern matching:

use regex::Regex;

let pattern = Regex::new(r"(\d{4})-(\d{2})-(\d{2})").unwrap();

if let Some(captures) = pattern.captures("Date: 2024-03-15") {
    let year = &captures[1];
    let month = &captures[2];
    let day = &captures[3];
    println!("{year}-{month}-{day}");
}

let dates: Vec<&str> = pattern
    .find_iter("Events on 2024-01-01 and 2024-06-15")
    .map(|matched| matched.as_str())
    .collect();

#Parsing Structured Formats

#CSV

use csv::ReaderBuilder;
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct Record {
    name: String,
    score: f64,
    grade: String,
}

fn read_scores(path: &str) -> Result<Vec<Record>, Box<dyn std::error::Error>> {
    let mut reader = ReaderBuilder::new()
        .has_headers(true)
        .from_path(path)?;

    let mut records = Vec::new();
    for result in reader.deserialize() {
        let record: Record = result?;
        records.push(record);
    }
    Ok(records)
}

#Log Parsing

A practical example of parsing Apache-style log lines:

fn parse_log_line(line: &str) -> Option<LogEntry> {
    let mut parts = line.splitn(4, ' ');

    let ip = parts.next()?;
    let timestamp = parts.next()?.trim_matches(|character| character == '[' || character == ']');
    let method_and_path = parts.next()?;
    let status_str = parts.next()?;

    let status: u16 = status_str.trim().parse().ok()?;

    Some(LogEntry {
        ip: ip.to_string(),
        timestamp: timestamp.to_string(),
        request: method_and_path.to_string(),
        status,
    })
}

struct LogEntry {
    ip: String,
    timestamp: String,
    request: String,
    status: u16,
}

#Nom — Parser Combinators

For complex grammars, nom lets you build parsers from small, composable pieces:

use nom::{
    bytes::complete::tag,
    character::complete::{alpha1, digit1},
    sequence::separated_pair,
    IResult,
};

fn parse_key_value(input: &str) -> IResult<&str, (&str, &str)> {
    separated_pair(alpha1, tag("="), digit1)(input)
}

let (remaining, (key, value)) = parse_key_value("port=8080").unwrap();
assert_eq!(key, "port");
assert_eq!(value, "8080");
assert_eq!(remaining, "");

#Summary

Start with string methods for simple cases. Reach for regex when patterns get complex. Use serde with format-specific crates for structured data (CSV, JSON, TOML). For custom grammars or binary formats, parser combinator libraries like nom or winnow are the right tools.