Como Usar Regex em Rust

Aprenda a usar expressões regulares em Rust com a crate regex: Regex::new, is_match, captures, replace e compilação lazy com LazyLock. Exemplos completos.

Expressões regulares (regex) são poderosas para buscar, validar e transformar texto. A crate regex do Rust é conhecida por sua performance excepcional e segurança — ela garante tempo de execução linear, sem risco de backtracking catastrófico. Nesta receita, você vai aprender a usar regex de forma eficiente em Rust.

Dependências

[package]
name = "receita-regex"
version = "0.1.0"
edition = "2021"

[dependencies]
regex = "1"

Código Completo

use regex::Regex;
use std::sync::LazyLock;

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

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

fn main() {
    // =============================================
    // 1. is_match() — verificar se há correspondência
    // =============================================
    let re = Regex::new(r"\d{3}\.\d{3}\.\d{3}-\d{2}").unwrap();

    println!("=== is_match ===");
    let cpfs = vec!["123.456.789-00", "12345678900", "abc.def.ghi-jk"];
    for cpf in &cpfs {
        println!("  '{}' é CPF formatado? {}", cpf, re.is_match(cpf));
    }

    // =============================================
    // 2. find() — encontrar a primeira correspondência
    // =============================================
    println!("\n=== find ===");
    let texto = "Meu telefone é (11) 98765-4321 e o fixo é (11) 3456-7890";
    if let Some(m) = RE_TELEFONE.find(texto) {
        println!("Primeiro telefone: '{}' (posição {}-{})", m.as_str(), m.start(), m.end());
    }

    // find_iter() — encontrar todas as correspondências
    println!("\n=== find_iter ===");
    for m in RE_TELEFONE.find_iter(texto) {
        println!("  Encontrado: '{}'", m.as_str());
    }

    // =============================================
    // 3. captures() — extrair grupos
    // =============================================
    println!("\n=== captures ===");
    let re_data = Regex::new(r"(\d{2})/(\d{2})/(\d{4})").unwrap();
    let texto = "Nascimento: 15/06/1990, Cadastro: 23/02/2026";

    for cap in re_data.captures_iter(texto) {
        println!(
            "  Data: {} | Dia: {} | Mês: {} | Ano: {}",
            &cap[0], &cap[1], &cap[2], &cap[3]
        );
    }

    // Grupos nomeados
    let re_nome = Regex::new(
        r"(?P<nome>[A-Z][a-záéíóúãõç]+)\s+(?P<sobrenome>[A-Z][a-záéíóúãõç]+)"
    ).unwrap();

    let texto = "Contato: Ana Silva e Carlos Santos";
    for cap in re_nome.captures_iter(texto) {
        println!(
            "  Nome: {} | Sobrenome: {}",
            &cap["nome"], &cap["sobrenome"]
        );
    }

    // =============================================
    // 4. replace() — substituir texto
    // =============================================
    println!("\n=== replace ===");

    // Substituir primeira ocorrência
    let re = Regex::new(r"\bRust\b").unwrap();
    let resultado = re.replace("Eu amo Rust e uso Rust todo dia", "Ferrugem");
    println!("  replace:     {}", resultado);

    // Substituir todas as ocorrências
    let resultado = re.replace_all("Eu amo Rust e uso Rust todo dia", "Ferrugem");
    println!("  replace_all: {}", resultado);

    // Substituição com referência a grupos capturados
    let re = Regex::new(r"(\d{2})/(\d{2})/(\d{4})").unwrap();
    let resultado = re.replace_all(
        "Data: 23/02/2026",
        "$3-$2-$1"  // Referenciar grupos por número
    );
    println!("  BR->ISO:     {}", resultado);

    // Substituição com closure
    let re = Regex::new(r"\b[a-z]+\b").unwrap();
    let resultado = re.replace_all("olá mundo rust", |caps: &regex::Captures| {
        caps[0].to_uppercase()
    });
    println!("  Maiúsculas:  {}", resultado);

    // =============================================
    // 5. split() — dividir texto por padrão
    // =============================================
    println!("\n=== split ===");
    let re = Regex::new(r"[,;\s]+").unwrap();
    let texto = "maçã, banana;  cereja   damasco, uva";
    let partes: Vec<&str> = re.split(texto).collect();
    println!("  Partes: {:?}", partes);

    // =============================================
    // 6. Validação com regex LazyLock
    // =============================================
    println!("\n=== Validação com LazyLock ===");
    let emails = vec![
        "usuario@exemplo.com",
        "invalido@",
        "nome.sobrenome@empresa.com.br",
        "@sem-nome.com",
    ];
    for email in &emails {
        println!("  '{}' válido? {}", email, RE_EMAIL.is_match(email));
    }

    // =============================================
    // 7. Extrair dados estruturados de texto
    // =============================================
    println!("\n=== Extrair dados ===");
    let log = r#"
        [2026-02-23 14:30:15] INFO: Servidor iniciado na porta 8080
        [2026-02-23 14:30:16] WARN: Cache expirado, recarregando
        [2026-02-23 14:30:17] ERROR: Conexão recusada pelo banco
    "#;

    let re_log = Regex::new(
        r"\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] (\w+): (.+)"
    ).unwrap();

    for cap in re_log.captures_iter(log) {
        println!(
            "  Hora: {} | Nível: {} | Msg: {}",
            &cap[1], &cap[2], cap[3].trim()
        );
    }

    // =============================================
    // 8. Case-insensitive e flags
    // =============================================
    println!("\n=== Flags ===");

    // (?i) — case insensitive
    let re = Regex::new(r"(?i)rust").unwrap();
    let texto = "Rust, RUST, rust, RuSt";
    let matches: Vec<&str> = re.find_iter(texto).map(|m| m.as_str()).collect();
    println!("  Case insensitive: {:?}", matches);

    // (?m) — multiline (^ e $ por linha)
    let re = Regex::new(r"(?m)^\d+").unwrap();
    let texto = "123 abc\n456 def\n789 ghi";
    let matches: Vec<&str> = re.find_iter(texto).map(|m| m.as_str()).collect();
    println!("  Multiline:        {:?}", matches);
}

Saída do Programa

=== is_match ===
  '123.456.789-00' é CPF formatado? true
  '12345678900' é CPF formatado? false
  'abc.def.ghi-jk' é CPF formatado? false

=== find ===
Primeiro telefone: '(11) 98765-4321' (posição 15-30)

=== find_iter ===
  Encontrado: '(11) 98765-4321'
  Encontrado: '(11) 3456-7890'

=== captures ===
  Data: 15/06/1990 | Dia: 15 | Mês: 06 | Ano: 1990
  Data: 23/02/2026 | Dia: 23 | Mês: 02 | Ano: 2026

=== replace ===
  replace:     Eu amo Ferrugem e uso Rust todo dia
  replace_all: Eu amo Ferrugem e uso Ferrugem todo dia
  BR->ISO:     Data: 2026-02-23
  Maiúsculas:  OLÁ MUNDO RUST

=== split ===
  Partes: ["maçã", "banana", "cereja", "damasco", "uva"]

=== Validação com LazyLock ===
  'usuario@exemplo.com' válido? true
  'invalido@' válido? false
  'nome.sobrenome@empresa.com.br' válido? true
  '@sem-nome.com' válido? false

=== Extrair dados ===
  Hora: 2026-02-23 14:30:15 | Nível: INFO | Msg: Servidor iniciado na porta 8080
  Hora: 2026-02-23 14:30:16 | Nível: WARN | Msg: Cache expirado, recarregando
  Hora: 2026-02-23 14:30:17 | Nível: ERROR | Msg: Conexão recusada pelo banco

=== Flags ===
  Case insensitive: ["Rust", "RUST", "rust", "RuSt"]
  Multiline:        ["123", "456", "789"]

Dicas de Performance

  • Compile a regex uma vez: use LazyLock<Regex> para regexes usadas repetidamente. Compilar regex é custoso.
  • A crate regex garante tempo linear: não há risco de backtracking exponencial como em outras linguagens.
  • Use is_match() quando só precisa saber se combina — é mais rápido que captures().
  • Prefira find_iter() a captures_iter() quando não precisa de grupos.

Veja Também