Iteración en Rust: cómo detenerse ante un error y devolver Result::Err

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: ¿por qué el iterador no quiere detenerse?

\n\n

Imagine: está escribiendo un programa en Rust que procesa una lista de números — analiza cadenas, lee archivos, realiza algunas transformaciones. Usa Iterator::map para recorrer cada elemento. Y de repente — ¡pum! — uno de los elementos se convierte en un error Result::Err. Sería lógico detener inmediatamente la iteración y devolver ese error hacia arriba. Pero map simplemente sigue funcionando como si nada. ¿Por qué? Y lo más importante: ¿cómo solucionarlo?

\n\n

Esta pregunta es una de las más populares en Stack Overflow entre los principiantes en Rust. Y con razón. Los mecanismos de manejo de errores en Rust son flexibles, pero requieren un enfoque consciente. En este artículo, analizaremos el problema en profundidad, veremos varias soluciones y elegiremos la más elegante. ¡Allá vamos!

\n\n

El problema: map no se detiene

\n\n

Empecemos con un ejemplo clásico. Supongamos que tenemos un vector de cadenas que queremos convertir en números:

\n\n
fn main() {\n    let inputs = vec!["42", "hello", "100"];\n\n    let result: Vec<Result<i32, std::num::ParseIntError>> =\n        inputs.iter().map(|s| s.parse::<i32>()).collect();\n\n    println!("{:?}", result);\n}
\n\n

Este código se compilará y ejecutará. Pero, ¿qué obtenemos? Un vector de Results: [Ok(42), Err(ParseIntError), Ok(100)]. La iteración no se detuvo en "hello", sino que continuó. Para muchas tareas, esto está bien — quiere recopilar todos los errores. Pero, ¿qué pasa si necesita la primera excepción? Por ejemplo, al cargar una configuración: si un parámetro es incorrecto, no tiene sentido seguir verificando.

\n\n

El problema es que map es una transformación perezosa. No sabe qué es "detenerse". Simplemente dice: "Dame un elemento, aplicaré la función, devolveré el resultado". Todo el control de pausa y continuación recae en el consumidor del iterador (collect, for, fold, etc.). Y collect para Vec simplemente va hasta el final.

\n\n

Solución 1: collect con FromIterator (la más popular)

\n\n

La respuesta más común en Stack Overflow es usar collect::<Result<Vec<_>, _>>(). Rust sabe que puede recopilar un iterador de Results en un solo Result con un vector dentro. Si encuentra un Err, la recopilación se detiene y se devuelve ese error.

\n\n
fn main() -> Result<(), std::num::ParseIntError> {\n    let inputs = vec!["42", "hello", "100"];\n\n    let result: Vec<i32> =\n        inputs.iter()\n              .map(|s| s.parse::<i32>())\n              .collect::<Result<Vec<_>, _>>()?; // <-- ¡magia!\n\n    println!("{:?}", result);\n    Ok(())\n}
\n\n

¿Cómo funciona? El tipo Result<Vec<i32>, ParseIntError> implementa el trait FromIterator. Cuando collect ve un Err, lo devuelve inmediatamente, sin procesar los elementos restantes. Si todo está bien, obtiene Ok(vector). Es breve, idiomático y eficiente.

\n\n

Desventaja: pierde información sobre en qué elemento ocurrió el error (a menos que lo envuelva en su propio tipo). Pero para scripts rápidos, es perfecto.

\n\n

Solución 2: filter_map para ignorar errores

\n\n

A veces no quiere detenerse, sino simplemente ignorar los elementos fallidos. Por ejemplo, contar solo las cadenas válidas. Para esto, filter_map es ideal:

\n\n
fn main() {\n    let inputs = vec!["42", "hello", "100", "abc", "7"];\n\n    let numbers: Vec<i32> = inputs.iter()\n        .filter_map(|s| s.parse::<i32>().ok()) // .ok() convierte Err en None\n        .collect();\n\n    println!("{:?}", numbers); // [42, 100, 7]\n}
\n\n

filter_map acepta

Blogs

Book Recommendations