Introducción: ¿Por qué son necesarias las expresiones regulares en Rust?
Las expresiones regulares (regex) son una herramienta poderosa para buscar, extraer y reemplazar texto según un patrón. En Rust, el trabajo con expresiones regulares se implementa a través del crate regex, que se considera uno de los más rápidos y seguros del ecosistema. A diferencia de muchos otros lenguajes, el compilador de Rust verifica la corrección de la expresión regular en tiempo de compilación (si se usa la macro regex!), lo que ayuda a evitar errores en tiempo de ejecución.
En este artículo, analizaremos ejemplos prácticos de uso de expresiones regulares en Rust: desde la búsqueda básica hasta la extracción compleja de datos. Aprenderá cómo configurar el proyecto, escribir patrones y procesar los resultados.
1. Configuración del proyecto y primera regex
Adición de la dependencia
Para empezar, agregue el crate regex al archivo Cargo.toml:
[dependencies]regex = "1"Después de esto, actualice las dependencias con el comando cargo build.
Primer ejemplo: búsqueda de una palabra
Escribamos un programa simple que encuentre la palabra "Rust" en una cadena:
use regex::Regex;
fn main() { let re = Regex::new(r"Rust").unwrap(); let text = "Estoy aprendiendo Rust y Python."; if re.is_match(text) { println!("¡Palabra 'Rust' encontrada!"); } else { println!("Palabra no encontrada."); }}La función Regex::new compila el patrón. Si el patrón no es válido, el programa terminará con un error (use unwrap() solo en prototipos).
2. Extracción de datos mediante capturas
Agrupación y grupos con nombre
Las expresiones regulares permiten extraer partes del texto usando paréntesis. Veamos un ejemplo con un email:
use regex::Regex;
fn main() { let re = Regex::new(r"(?P<name>[a-zA-Z0-9._%+-]+)@(?P<domain>[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})").unwrap(); let text = "Mi email: user@example.com, y también admin@test.org."; for caps in re.captures_iter(text) { println!("Nombre: {}", &caps["name"]); println!("Dominio: {}", &caps["domain"]); }}En este ejemplo, usamos grupos con nombre (?P<name>...). Esto hace que el código sea más legible y conveniente para el procesamiento posterior.
Extracción de todas las coincidencias
Si necesita obtener todas las coincidencias sin grupos, use find_iter:
use regex::Regex;
fn main() { let re = Regex::new(r"\\d+").unwrap(); let text = "Precios: 100, 250 y 500 rublos."; for mat in re.find_iter(text) { println!("Número encontrado: {}", mat.as_str()); }}El método find_iter devuelve un iterador sobre objetos Match, que contienen las posiciones inicial y final, así como el texto de la coincidencia.
3. Reemplazo de texto y patrones complejos
Reemplazo simple
Reemplacemos todos los dígitos por la palabra "NUMBER":
use regex::Regex;
fn main() { let re = Regex::new(r"\\d+").unwrap(); let text = "Código: 12345, estado: 0."; let result = re.replace_all(text, "NUMBER"); println!("{}", result); // "Código: NUMBER, estado: NUMBER."}Reemplazo usando capturas
Se puede hacer referencia a los grupos en la cadena de reemplazo usando $1, $2 o grupos con nombre $name:
use regex::Regex;
fn main() { let re = Regex::new(r"(?P<year>\\d{4})-(?P<month>\\d{2})-(?P<day>\\d{2})").unwrap(); let text = "Fecha: 2024-03-15"; let result = re.replace_all(text, "$day.$month.$year"); println!("{}", result); // "Fecha: 15.03.2024"}Patrón complejo: análisis de logs
Veamos un ejemplo real: el análisis de una línea de log de un servidor web:
use regex::Regex;
fn main() { let log_line =