Regex: Expressões Regulares de Alta Performance em Rust

Guia completo da crate regex em Rust. Aprenda matching, capturas, substituição, RegexSet, lazy_static, performance e exemplos práticos de parsing e validação.

A crate regex é a implementação padrão de expressões regulares para Rust, desenvolvida por Andrew Gallick (BurntSushi), o mesmo autor de ripgrep. Ela é conhecida por sua segurança (sem backtracking exponencial, garantia de tempo linear), performance excepcional e conformidade com a sintaxe RE2 do Google.

Expressões regulares são essenciais para validação de dados, parsing de texto, busca em logs e transformação de strings. Em Rust, a crate regex é usada extensivamente em ferramentas como ripgrep, que supera grep e ag em benchmarks reais.

Instalação

Adicione ao seu Cargo.toml:

[dependencies]
regex = "1"

Para compilar expressões regulares uma única vez e reutilizá-las (recomendado), use também:

[dependencies]
regex = "1"
once_cell = "1"

Ou, se preferir lazy_static:

[dependencies]
regex = "1"
lazy_static = "1"

Uso Básico

Verificando se um Padrão Existe

use regex::Regex;

fn main() {
    let re = Regex::new(r"^\d{3}\.\d{3}\.\d{3}-\d{2}$").unwrap();

    assert!(re.is_match("123.456.789-00"));
    assert!(!re.is_match("12345678900"));
    assert!(!re.is_match("abc.def.ghi-jk"));

    println!("Validação de CPF funcionando!");
}

Note o uso de r"..." (raw string) para evitar escapar barras invertidas.

Encontrando a Primeira Correspondência

use regex::Regex;

fn main() {
    let re = Regex::new(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b").unwrap();
    let texto = "Servidor em 192.168.1.100, backup em 10.0.0.5";

    if let Some(m) = re.find(texto) {
        println!("IP encontrado: '{}' na posição {}..{}", m.as_str(), m.start(), m.end());
        // Saída: IP encontrado: '192.168.1.100' na posição 13..26
    }
}

Encontrando Todas as Correspondências

use regex::Regex;

fn main() {
    let re = Regex::new(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b").unwrap();
    let texto = "Contatos: maria@exemplo.com, joao@empresa.com.br e suporte@ajuda.org";

    let emails: Vec<&str> = re.find_iter(texto).map(|m| m.as_str()).collect();
    println!("Emails encontrados: {:?}", emails);
    // Saída: ["maria@exemplo.com", "joao@empresa.com.br", "suporte@ajuda.org"]
}

Grupos de Captura

use regex::Regex;

fn main() {
    let re = Regex::new(r"(\d{2})/(\d{2})/(\d{4})").unwrap();
    let texto = "Data de nascimento: 15/03/1990";

    if let Some(caps) = re.captures(texto) {
        let dia = &caps[1];
        let mes = &caps[2];
        let ano = &caps[3];
        println!("Dia: {}, Mês: {}, Ano: {}", dia, mes, ano);
        // Saída: Dia: 15, Mês: 03, Ano: 1990

        // Grupo 0 é sempre o match completo
        println!("Match completo: {}", &caps[0]);
        // Saída: 15/03/1990
    }
}

Capturas Nomeadas

use regex::Regex;

fn main() {
    let re = Regex::new(
        r"(?P<protocolo>https?)://(?P<host>[^/]+)(?P<caminho>/[^\s]*)?"
    ).unwrap();

    let urls = vec![
        "https://www.exemplo.com.br/pagina",
        "http://api.servico.com/v1/usuarios",
        "https://rust-lang.org",
    ];

    for url in urls {
        if let Some(caps) = re.captures(url) {
            println!("URL: {}", url);
            println!("  Protocolo: {}", &caps["protocolo"]);
            println!("  Host: {}", &caps["host"]);
            println!("  Caminho: {}", caps.name("caminho").map_or("/", |m| m.as_str()));
            println!();
        }
    }
}

Iterando sobre Capturas

use regex::Regex;

fn main() {
    let re = Regex::new(r"(?P<chave>\w+)=(?P<valor>[^&]+)").unwrap();
    let query = "nome=Maria&idade=30&cidade=São+Paulo&ativo=true";

    println!("Parâmetros da query string:");
    for caps in re.captures_iter(query) {
        println!("  {} = {}", &caps["chave"], &caps["valor"]);
    }
}

Recursos Avançados

Substituição de Texto

use regex::Regex;

fn main() {
    let re = Regex::new(r"\b(\w)").unwrap();

    // Substituição simples
    let censurado = Regex::new(r"\d{3}\.\d{3}\.\d{3}-\d{2}")
        .unwrap()
        .replace_all("CPF: 123.456.789-00", "***.***.***-**");
    println!("{}", censurado);
    // Saída: CPF: ***.***.***-**

    // Substituição com referências a grupos
    let re_data = Regex::new(r"(\d{2})/(\d{2})/(\d{4})").unwrap();
    let iso = re_data.replace_all("Data: 15/03/2024", "$3-$2-$1");
    println!("{}", iso);
    // Saída: Data: 2024-03-15

    // Substituição com capturas nomeadas
    let re_nome = Regex::new(r"(?P<sobrenome>\w+),\s*(?P<nome>\w+)").unwrap();
    let formatado = re_nome.replace_all("Silva, Maria", "$nome $sobrenome");
    println!("{}", formatado);
    // Saída: Maria Silva
}

Substituição com Closure

use regex::{Captures, Regex};

fn main() {
    let re = Regex::new(r"\b\w+\b").unwrap();

    // Capitalizar cada palavra
    let resultado = re.replace_all("olá mundo rust", |caps: &Captures| {
        let palavra = &caps[0];
        let mut chars = palavra.chars();
        match chars.next() {
            None => String::new(),
            Some(c) => c.to_uppercase().to_string() + chars.as_str(),
        }
    });
    println!("{}", resultado);
    // Saída: Olá Mundo Rust

    // Substituição condicional
    let re_num = Regex::new(r"\d+").unwrap();
    let resultado = re_num.replace_all("Tem 3 gatos e 15 cães", |caps: &Captures| {
        let num: u32 = caps[0].parse().unwrap();
        if num > 10 {
            format!("MUITOS({})", num)
        } else {
            caps[0].to_string()
        }
    });
    println!("{}", resultado);
    // Saída: Tem 3 gatos e MUITOS(15) cães
}

Compilação Lazy com once_cell

Compilar regex é custoso. Use once_cell para compilar uma única vez:

use once_cell::sync::Lazy;
use regex::Regex;

static RE_EMAIL: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap()
});

static RE_CPF: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"^\d{3}\.\d{3}\.\d{3}-\d{2}$").unwrap()
});

static RE_TELEFONE: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"^\(?(\d{2})\)?\s?(\d{4,5})-?(\d{4})$").unwrap()
});

fn validar_email(email: &str) -> bool {
    RE_EMAIL.is_match(email)
}

fn validar_cpf(cpf: &str) -> bool {
    RE_CPF.is_match(cpf)
}

fn formatar_telefone(tel: &str) -> Option<String> {
    RE_TELEFONE.captures(tel).map(|caps| {
        format!("({}) {}-{}", &caps[1], &caps[2], &caps[3])
    })
}

fn main() {
    println!("Email válido: {}", validar_email("maria@exemplo.com"));
    println!("CPF válido: {}", validar_cpf("123.456.789-00"));
    println!("Telefone: {:?}", formatar_telefone("11987654321"));
    // Saída: Telefone: Some("(11) 98765-4321")
}

RegexSet para Múltiplos Padrões

O RegexSet permite testar múltiplos padrões simultaneamente de forma eficiente:

use regex::RegexSet;

fn main() {
    let tipos_log = RegexSet::new(&[
        r"(?i)\berror\b",     // 0
        r"(?i)\bwarn(ing)?\b", // 1
        r"(?i)\binfo\b",      // 2
        r"(?i)\bdebug\b",     // 3
        r"(?i)\btrace\b",     // 4
    ]).unwrap();

    let linhas = vec![
        "2024-01-15 ERROR: Conexão recusada",
        "2024-01-15 WARN: Memória alta",
        "2024-01-15 INFO: Servidor iniciado",
        "2024-01-15 DEBUG: Query executada",
        "Linha sem nível de log",
    ];

    let nomes = ["ERROR", "WARN", "INFO", "DEBUG", "TRACE"];

    for linha in linhas {
        let matches: Vec<&str> = tipos_log
            .matches(linha)
            .into_iter()
            .map(|i| nomes[i])
            .collect();

        if matches.is_empty() {
            println!("'{}' -> Nenhum nível detectado", linha);
        } else {
            println!("'{}' -> {:?}", linha, matches);
        }
    }
}

Flags e Modificadores

use regex::Regex;

fn main() {
    // Case insensitive
    let re = Regex::new(r"(?i)rust").unwrap();
    assert!(re.is_match("RUST"));
    assert!(re.is_match("Rust"));
    assert!(re.is_match("rust"));

    // Multiline: ^ e $ casam com início/fim de cada linha
    let re = Regex::new(r"(?m)^\d+\.\s+(.+)$").unwrap();
    let texto = "Lista:\n1. Primeiro\n2. Segundo\n3. Terceiro";
    for caps in re.captures_iter(texto) {
        println!("Item: {}", &caps[1]);
    }

    // Dot-all: . casa com \n também
    let re = Regex::new(r"(?s)<div>(.+?)</div>").unwrap();
    let html = "<div>\n  Conteúdo\n  multilinha\n</div>";
    if let Some(caps) = re.captures(html) {
        println!("Conteúdo: {:?}", &caps[1]);
    }

    // Combinando flags
    let re = Regex::new(r"(?ims)^início.*fim$").unwrap();
    assert!(re.is_match("INÍCIO da\nlinha FIM"));
}

Split com Regex

use regex::Regex;

fn main() {
    // Split por múltiplos separadores
    let re = Regex::new(r"[,;\s]+").unwrap();
    let texto = "maçã, banana; laranja  uva,manga";
    let frutas: Vec<&str> = re.split(texto).collect();
    println!("{:?}", frutas);
    // Saída: ["maçã", "banana", "laranja", "uva", "manga"]

    // Split com limite
    let partes: Vec<&str> = re.splitn(texto, 3).collect();
    println!("{:?}", partes);
    // Saída: ["maçã", "banana", "laranja  uva,manga"]
}

Boas Práticas

1. Sempre Compile Regex Fora de Loops

use regex::Regex;

// RUIM: compila a regex a cada chamada
fn validar_ruim(emails: &[&str]) -> Vec<bool> {
    emails.iter().map(|e| {
        let re = Regex::new(r"^[\w.+-]+@[\w-]+\.[\w.]+$").unwrap();
        re.is_match(e)
    }).collect()
}

// BOM: compila uma vez, reutiliza
fn validar_bom(emails: &[&str]) -> Vec<bool> {
    let re = Regex::new(r"^[\w.+-]+@[\w-]+\.[\w.]+$").unwrap();
    emails.iter().map(|e| re.is_match(e)).collect()
}

// MELHOR: usa Lazy para evitar recompilação entre chamadas
use once_cell::sync::Lazy;
static RE_EMAIL: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"^[\w.+-]+@[\w-]+\.[\w.]+$").unwrap()
});

fn validar_melhor(emails: &[&str]) -> Vec<bool> {
    emails.iter().map(|e| RE_EMAIL.is_match(e)).collect()
}

2. Prefira Métodos Simples para Padrões Simples

fn main() {
    let texto = "Hello, World!";

    // RUIM: regex para padrão trivial
    // let re = Regex::new(r"World").unwrap();
    // let encontrou = re.is_match(texto);

    // BOM: métodos de String são mais rápidos para buscas simples
    let encontrou = texto.contains("World");

    // RUIM: regex para starts_with/ends_with
    // let re = Regex::new(r"^Hello").unwrap();

    // BOM:
    let comeca = texto.starts_with("Hello");

    println!("Encontrou: {}, Começa: {}", encontrou, comeca);
}

3. Use Grupos Não-Capturantes Quando Não Precisa Capturar

use regex::Regex;

fn main() {
    // Grupo capturante desnecessário — gasta memória
    // let re = Regex::new(r"(https?|ftp)://(\S+)").unwrap();

    // Grupo não-capturante para alternação
    let re = Regex::new(r"(?:https?|ftp)://(\S+)").unwrap();
    let texto = "Acesse https://exemplo.com agora";
    if let Some(caps) = re.captures(texto) {
        // caps[1] é o host, não o protocolo
        println!("URL: {}", &caps[1]);
    }
}

4. Cuidado com Padrões que Casam Strings Vazias

use regex::Regex;

fn main() {
    // PERIGOSO: * permite match vazio, gerando resultados inesperados
    let re = Regex::new(r"\d*").unwrap();
    let matches: Vec<&str> = re.find_iter("abc").map(|m| m.as_str()).collect();
    println!("{:?}", matches); // ["", "", "", ""] — provavelmente não é o que você quer

    // MELHOR: use + para exigir pelo menos um dígito
    let re = Regex::new(r"\d+").unwrap();
    let matches: Vec<&str> = re.find_iter("abc123def456").map(|m| m.as_str()).collect();
    println!("{:?}", matches); // ["123", "456"]
}

5. Valide Regex em Tempo de Compilação Quando Possível

Se sua regex é constante, considere validar em compile-time usando testes:

#[cfg(test)]
mod tests {
    use regex::Regex;

    #[test]
    fn regex_validas() {
        // Se alguma regex for inválida, o teste falha
        Regex::new(r"^\d{3}\.\d{3}\.\d{3}-\d{2}$").unwrap();
        Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap();
        Regex::new(r"^\(\d{2}\)\s?\d{4,5}-\d{4}$").unwrap();
    }
}

Exemplos Práticos

Exemplo 1: Parser de Log

use once_cell::sync::Lazy;
use regex::Regex;
use std::collections::HashMap;

static RE_LOG: Lazy<Regex> = Lazy::new(|| {
    Regex::new(
        r"(?P<timestamp>\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}(?:\.\d+)?)\s+(?P<nivel>\w+)\s+\[(?P<modulo>[^\]]+)\]\s+(?P<mensagem>.+)"
    ).unwrap()
});

#[derive(Debug)]
struct EntradaLog {
    timestamp: String,
    nivel: String,
    modulo: String,
    mensagem: String,
}

fn parsear_log(linha: &str) -> Option<EntradaLog> {
    RE_LOG.captures(linha).map(|caps| EntradaLog {
        timestamp: caps["timestamp"].to_string(),
        nivel: caps["nivel"].to_string(),
        modulo: caps["modulo"].to_string(),
        mensagem: caps["mensagem"].to_string(),
    })
}

fn analisar_logs(logs: &str) -> HashMap<String, usize> {
    let mut contagem: HashMap<String, usize> = HashMap::new();

    for linha in logs.lines() {
        if let Some(entrada) = parsear_log(linha) {
            *contagem.entry(entrada.nivel).or_insert(0) += 1;
        }
    }

    contagem
}

fn main() {
    let logs = r#"
2024-01-15 10:30:00.123 INFO [servidor::http] Requisição GET /api/users - 200 OK
2024-01-15 10:30:01.456 ERROR [servidor::db] Falha na query: timeout após 30s
2024-01-15 10:30:02.789 WARN [servidor::cache] Cache miss para chave 'user:42'
2024-01-15 10:30:03.012 INFO [servidor::http] Requisição POST /api/users - 201 Created
2024-01-15 10:30:04.345 DEBUG [servidor::auth] Token validado para user_id=42
2024-01-15 10:30:05.678 ERROR [servidor::http] Requisição GET /api/admin - 403 Forbidden
2024-01-15 10:30:06.901 INFO [servidor::http] Requisição GET /health - 200 OK
"#;

    println!("=== Parsing de Logs ===\n");

    for linha in logs.trim().lines() {
        if let Some(entrada) = parsear_log(linha) {
            println!(
                "[{}] {} ({}) -> {}",
                entrada.nivel, entrada.timestamp, entrada.modulo, entrada.mensagem
            );
        }
    }

    println!("\n=== Estatísticas ===\n");
    let stats = analisar_logs(logs);
    for (nivel, contagem) in &stats {
        println!("  {}: {} ocorrências", nivel, contagem);
    }
}

Exemplo 2: Validador de Dados Brasileiro

use once_cell::sync::Lazy;
use regex::Regex;

static RE_CPF: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"^(\d{3})\.(\d{3})\.(\d{3})-(\d{2})$").unwrap()
});

static RE_CNPJ: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"^(\d{2})\.(\d{3})\.(\d{3})/(\d{4})-(\d{2})$").unwrap()
});

static RE_CEP: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"^(\d{5})-(\d{3})$").unwrap()
});

static RE_TELEFONE: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"^\((\d{2})\)\s?(\d{4,5})-(\d{4})$").unwrap()
});

static RE_PLACA: Lazy<Regex> = Lazy::new(|| {
    // Placa antiga (ABC-1234) e Mercosul (ABC1D23)
    Regex::new(r"^[A-Z]{3}-?\d[A-Z0-9]\d{2}$").unwrap()
});

#[derive(Debug)]
enum TipoDocumento {
    Cpf,
    Cnpj,
    Cep,
    Telefone,
    PlacaVeiculo,
}

#[derive(Debug)]
struct ResultadoValidacao {
    tipo: TipoDocumento,
    valido: bool,
    formatado: Option<String>,
    detalhes: String,
}

fn validar_cpf(input: &str) -> ResultadoValidacao {
    let digitos: String = input.chars().filter(|c| c.is_ascii_digit()).collect();

    if digitos.len() != 11 {
        return ResultadoValidacao {
            tipo: TipoDocumento::Cpf,
            valido: false,
            formatado: None,
            detalhes: format!("CPF deve ter 11 dígitos, encontrados {}", digitos.len()),
        };
    }

    // Verifica se todos os dígitos são iguais
    if digitos.chars().all(|c| c == digitos.chars().next().unwrap()) {
        return ResultadoValidacao {
            tipo: TipoDocumento::Cpf,
            valido: false,
            formatado: None,
            detalhes: "CPF com todos os dígitos iguais é inválido".to_string(),
        };
    }

    let formatado = format!(
        "{}.{}.{}-{}",
        &digitos[0..3],
        &digitos[3..6],
        &digitos[6..9],
        &digitos[9..11]
    );

    ResultadoValidacao {
        tipo: TipoDocumento::Cpf,
        valido: RE_CPF.is_match(&formatado),
        formatado: Some(formatado),
        detalhes: "Formato válido".to_string(),
    }
}

fn validar_telefone(input: &str) -> ResultadoValidacao {
    let digitos: String = input.chars().filter(|c| c.is_ascii_digit()).collect();

    if digitos.len() < 10 || digitos.len() > 11 {
        return ResultadoValidacao {
            tipo: TipoDocumento::Telefone,
            valido: false,
            formatado: None,
            detalhes: format!(
                "Telefone deve ter 10 ou 11 dígitos, encontrados {}",
                digitos.len()
            ),
        };
    }

    let formatado = if digitos.len() == 11 {
        format!("({}) {}-{}", &digitos[0..2], &digitos[2..7], &digitos[7..11])
    } else {
        format!("({}) {}-{}", &digitos[0..2], &digitos[2..6], &digitos[6..10])
    };

    ResultadoValidacao {
        tipo: TipoDocumento::Telefone,
        valido: RE_TELEFONE.is_match(&formatado),
        formatado: Some(formatado),
        detalhes: "Formato válido".to_string(),
    }
}

fn main() {
    println!("=== Validador de Dados Brasileiros ===\n");

    let cpfs = vec!["123.456.789-09", "12345678909", "111.111.111-11", "abc"];
    for cpf in cpfs {
        let resultado = validar_cpf(cpf);
        println!("CPF '{}': válido={}, formatado={:?}, detalhe={}",
                 cpf, resultado.valido, resultado.formatado, resultado.detalhes);
    }

    println!();

    let telefones = vec!["(11) 98765-4321", "11987654321", "(21) 3456-7890", "123"];
    for tel in telefones {
        let resultado = validar_telefone(tel);
        println!("Tel '{}': válido={}, formatado={:?}",
                 tel, resultado.valido, resultado.formatado);
    }

    println!();

    // Validação de CEP
    let ceps = vec!["01310-100", "12345-678", "1234567", "abcde-fgh"];
    for cep in ceps {
        let valido = RE_CEP.is_match(cep);
        println!("CEP '{}': válido={}", cep, valido);
    }

    println!();

    // Validação de placas
    let placas = vec!["ABC-1234", "ABC1D23", "XYZ-9999", "AB-1234"];
    for placa in placas {
        let valido = RE_PLACA.is_match(placa);
        println!("Placa '{}': válido={}", placa, valido);
    }
}

Exemplo 3: Extrator de Dados de Texto

use once_cell::sync::Lazy;
use regex::Regex;
use std::collections::HashMap;

static RE_PRECO: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"R\$\s?(\d{1,3}(?:\.\d{3})*(?:,\d{2})?)").unwrap()
});

static RE_DATA: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"\b(\d{2})/(\d{2})/(\d{4})\b").unwrap()
});

static RE_HORA: Lazy<Regex> = Lazy::new(|| {
    Regex::new(r"\b(\d{2}):(\d{2})(?::(\d{2}))?\b").unwrap()
});

fn extrair_precos(texto: &str) -> Vec<f64> {
    RE_PRECO
        .captures_iter(texto)
        .filter_map(|caps| {
            let valor_str = caps[1].replace('.', "").replace(',', ".");
            valor_str.parse::<f64>().ok()
        })
        .collect()
}

fn extrair_datas(texto: &str) -> Vec<String> {
    RE_DATA
        .captures_iter(texto)
        .map(|caps| format!("{}-{}-{}", &caps[3], &caps[2], &caps[1]))
        .collect()
}

fn substituir_dados_sensiveis(texto: &str) -> String {
    let re_cpf = Regex::new(r"\d{3}\.\d{3}\.\d{3}-\d{2}").unwrap();
    let re_email = Regex::new(r"[\w.+-]+@[\w-]+\.[\w.]+").unwrap();
    let re_tel = Regex::new(r"\(\d{2}\)\s?\d{4,5}-\d{4}").unwrap();

    let resultado = re_cpf.replace_all(texto, "***.***.***-**");
    let resultado = re_email.replace_all(&resultado, "[EMAIL REDACTED]");
    let resultado = re_tel.replace_all(&resultado, "[TEL REDACTED]");

    resultado.to_string()
}

fn main() {
    let nota_fiscal = r#"
NOTA FISCAL ELETRÔNICA
Data: 15/03/2024  Hora: 14:30:00

Cliente: Maria Silva
CPF: 123.456.789-00
Email: maria@exemplo.com
Telefone: (11) 98765-4321

Itens:
1. Notebook Dell     - R$ 4.599,90
2. Mouse Logitech   - R$ 189,90
3. Teclado Mecânico  - R$ 459,00
4. Monitor 27"      - R$ 2.199,00

Subtotal: R$ 7.447,80
Desconto: R$ 744,78
Total: R$ 6.703,02

Próxima entrega: 20/03/2024
"#;

    println!("=== Preços encontrados ===");
    let precos = extrair_precos(nota_fiscal);
    for (i, preco) in precos.iter().enumerate() {
        println!("  {}: R$ {:.2}", i + 1, preco);
    }
    println!("  Soma: R$ {:.2}", precos.iter().sum::<f64>());

    println!("\n=== Datas encontradas (ISO 8601) ===");
    let datas = extrair_datas(nota_fiscal);
    for data in &datas {
        println!("  {}", data);
    }

    println!("\n=== Dados sensíveis removidos ===");
    let censurado = substituir_dados_sensiveis(nota_fiscal);
    println!("{}", censurado);
}

Comparação com Alternativas

CrateCaso de usoPerformanceBacktracking
regexUso geralExcelente (tempo linear)Não (seguro)
fancy-regexLookahead/lookbehindBoaSim (pode ser lento)
pcre2Compatibilidade PCREMuito boaSim
nomParsing complexoExcelenteN/A (parser combinator)
pestGramáticas PEGBoaN/A (PEG parser)

Use regex quando precisar de expressões regulares padrão com garantia de performance. Use fancy-regex se precisar de lookahead/lookbehind. Para parsing complexo com gramáticas, considere nom ou pest.

Conclusão

A crate regex é uma ferramenta indispensável no arsenal de todo desenvolvedor Rust. Sua combinação de performance garantida (tempo linear), API ergonômica e ampla cobertura da sintaxe de expressões regulares a torna ideal para validação, parsing e transformação de texto.

Lembre-se de sempre compilar suas regex fora de loops (preferencialmente com once_cell::sync::Lazy), preferir métodos de String para padrões simples, e usar RegexSet quando precisar testar múltiplos padrões.

Próximos passos:

  • Explore a crate serde para combinar regex com serialização de dados
  • Veja chrono para parsear datas extraídas com regex
  • Confira a documentação completa da sintaxe regex