ProgramaciónProgramador de sistemas

Explica cómo se maneja la gestión de errores a través de Result<T, E> y cómo utilizar correctamente el operador de interrogación (?), sin comprometer la seguridad y la legibilidad del código.

Supere entrevistas con el asistente de IA Hintsage

Respuesta.

Rust se basa en la gestión explícita de errores: las excepciones están ausentes, en su lugar se utiliza el valor de retorno Result<T, E>. Esto garantiza la seguridad y la previsibilidad del código.

Historia del tema:

Muchos lenguajes han seguido el camino de las excepciones, lo que ha llevado a situaciones inesperadas y a la necesidad de manejar las excepciones de forma explícita en tiempo de ejecución. Desde el principio, Rust apostó por el control a través de tipos, todos los errores deben ser parte de la firma de la función.

Problema:

El objetivo principal es escribir código en el que los errores no sean ignorados ni enmascarados, no existen funciones "que fallen", pero al mismo tiempo el código sigue siendo compacto y legible. Es necesario propagar correctamente los errores hacia arriba, sin perder información sobre su tipo, y sin confundir la lógica.

Solución:

La herramienta clave es el tipo Result<T, E> y el operador ?, que "desenvuelve" automáticamente el resultado: en caso de error, se produce una salida inmediata de la función con la devolución del error, y en caso de éxito se devuelve el valor.

Ejemplo de código:

fn read_number(file: &str) -> Result<i32, std::io::Error> { let content = std::fs::read_to_string(file)?; let num: i32 = content.trim().parse()?; Ok(num) }

Características clave:

  • Declaración explícita de todos los errores potenciales en la firma
  • El operador ? permite simplificar la anidación (elimina if-let/unwrap/expect)
  • Los errores pueden ser fácilmente "envueltos" o transformados (a través de .map_err() y otros métodos)

Preguntas capciosas.

¿Se puede usar ? en funciones que devuelven Unit (void)?

No, el operador ? solo es válido dentro de funciones que retornan Result o Option. Si la función devuelve (), no se puede usar ?.

¿Qué sucederá si los tipos de error en varias llamadas con ? son diferentes?

Se producirá un error de compilación: el tipo de error debe estar claramente definido. Se debe o bien convertir todos los errores a un único tipo a través de .map_err() o usar thiserror, o describir un envoltorio enum a nivel de API. Ejemplo:

fn foo() -> Result<_, MyError> { let a = bar()?; let b = baz().map_err(MyError::Baz)?; Ok() }

¿Qué hay de peligroso en .unwrap() en la lógica interna?

Existe una creencia errónea de que en el código "principal" se puede usar unwrap() sin cuidado, "definitivamente no fallará". En realidad, incluso un pequeño error invisible puede causar pánico en tiempo de ejecución, afectando la seguridad del programa.

Errores comunes y anti-patrones

  • Capturar todos los errores a través de unwrap/expect, especialmente en la lógica interna
  • Pérdida de contexto al usar map_err(|_| …), cuando el error se enmascara sin guardar la información original
  • Anidamiento excesivo al manejar cada error manualmente sin ?

Ejemplo de la vida real

Caso negativo

En el código de producción se dejaron muchos unwrap() después de una depuración rápida. Como resultado, la aplicación fallaba en cualquier error de análisis debido a una entrada incorrecta del usuario.

Pros:

  • Depuración rápida

Contras:

  • Posible caída de la aplicación, difícil encontrar la causa

Caso positivo

Se utilizó toda la pilas Result<T, E> con una descripción explícita de errores y se aplicaron solo ? y .map_err(), manteniendo toda la información sobre fallos.

Pros:

  • Facilidad de depuración, comportamiento predecible

Contras:

  • Un poco más de tipos de errores "ruidosos"