Result en Rust: el manejo de errores sin excepciones

En Java o Python, una función puede lanzar una excepción en cualquier momento y el compilador no te obliga a manejarla. En Rust, los errores esperados se representan con Result<T, E>: un enum que el compilador te obliga a inspeccionar. Si una función puede fallar, su tipo de retorno lo dice explícitamente. No hay excepciones ocultas.

La definición de Result

enum Result<T, E> {
    Ok(T),   // operación exitosa, contiene el valor T
    Err(E),  // operación fallida, contiene el error E
}

Ejemplo: leer un fichero

use std::fs;
use std::io;

fn leer_fichero(ruta: &str) -> Result<String, io::Error> {
    fs::read_to_string(ruta)
}

fn main() {
    match leer_fichero("datos.txt") {
        Ok(contenido) => println!("Contenido:n{}", contenido),
        Err(e)        => println!("Error al leer: {}", e),
    }
}

El tipo de retorno Result<String, io::Error> indica que puede devolver un String o un io::Error. El llamador sabe que puede fallar antes de leer una sola línea de implementación.

match para manejar ambos casos

fn parsear_numero(s: &str) -> Result<i32, std::num::ParseIntError> {
    s.trim().parse()
}

fn main() {
    let resultado = parsear_numero("42");
    match resultado {
        Ok(n)  => println!("Número: {}", n),
        Err(e) => println!("Error: {}", e),
    }
}

unwrap y expect

Si estás seguro de que el resultado es Ok, puedes extraer el valor con unwrap() o expect():

// Hace panic si es Err
let n: i32 = "42".parse().unwrap();

// Hace panic con mensaje personalizado
let n: i32 = "42".parse().expect("Debería ser un número válido");

En producción, evita unwrap() salvo en tests o cuando tengas certeza lógica absoluta.

Métodos de Result

let r: Result<i32, &str> = Ok(10);

// unwrap_or: valor por defecto si Err
let v = r.unwrap_or(0);

// map: transforma el Ok
let doble = r.map(|n| n * 2); // Ok(20)

// map_err: transforma el Err
let r2: Result<i32, String> = r.map_err(|e| format!("Error: {}", e));

// and_then: encadena operaciones que pueden fallar
let resultado = "42".parse::<i32>()
    .and_then(|n| if n > 0 { Ok(n * 2) } else { Err("número negativo".parse().unwrap()) });

Múltiples tipos de error

use std::num::ParseIntError;
use std::io;

fn leer_numero_de_fichero(ruta: &str) -> Result<i32, Box<dyn std::error::Error>> {
    let contenido = fs::read_to_string(ruta)?;
    let numero: i32 = contenido.trim().parse()?;
    Ok(numero)
}

Box<dyn std::error::Error> es el tipo de error comodín que acepta cualquier error. Perfecto para aplicaciones donde no necesitas distinguir el tipo exacto del error.

Comparativa con try/catch

AspectoJava/Python (excepciones)Rust (Result)
Error visible en firmaSolo con checked exceptions (Java)Siempre
Olvidar manejar el errorPosible (runtime crash)Warning del compilador
Control de flujo ocultoSí (throw puede venir de cualquier lado)No
Coste en runtimeStack unwinding (caro)Cero overhead (es un enum)

Resumen

  • Result<T, E> es un enum con Ok(T) y Err(E).
  • El compilador te obliga a inspeccionar el resultado.
  • Métodos útiles: map, map_err, and_then, unwrap_or.
  • Para múltiples tipos de error: Box<dyn Error>.
  • El operador ? propaga errores sin escribir un match en cada línea.

El siguiente artículo se centra en el operador ?: cómo funciona internamente, cómo convierte tipos de error y por qué simplifica tanto el código de manejo de errores en Rust.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP