Manejo de Excepciones

En .Net, una excepción es un tipo que hereda de la clase System.Exception. Excepción es lanzada si un problema ocurre en una sección de código. Un lanzamiento de excepción es pasado hacia arriba al stack hasta que la aplicación la maneje o el programa termine.

Rust no tiene excepciones, pero distingue entre errores recuperables y no recuperables en su lugar. Un error recuperable representa un problema que debe ser reportado, pero sin embargo el programa continua. Resultado de operaciones que pueden fallar con errores recuperables son de tipo Result<T, E>, en donde Ees del tipo de variante de error. La macro panic! detiene la ejecución cuando el programa encuentra un error irrecuperable. Un error irrecuperable es siempre un síntoma de un bug.

Tipos de errores personalizados

En .Net, una excepción personalizada deriva de la clase Exception. La documentación en Cómo crear excepciones definidas por el usuario menciona el siguiente ejemplo:

public class EmployeeListNotFoundException : Exception
{
    public EmployeeListNotFoundException() { }

    public EmployeeListNotFoundException(string message)
        : base(message) { }

    public EmployeeListNotFoundException(string message, Exception inner)
        : base(message, inner) { }
}

En Rust, uno puede implementar el comportamiento básico para los valores erróneos via implementación de el trait Error. La implementación minima definida por el usuario en Rust:

#[derive(Debug)]
pub struct EmployeeListNotFound;

impl std::fmt::Display for EmployeeListNotFound {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("No se pudo encontrar empleado en la lista.")
    }
}

impl std::error::Error for EmployeeListNotFound {}

El equivalente para la Exception.InnerException de .Net es el método Error::source() en Rust. Sin embargo, este no requiere proveer una implementación para Error::source(), la implementación por defecto (blanket implementation) retorna un None.

Elevando excepciones

Para elevar una excepción en C#, lanza una instancia de la excepción:

void ThrowIfNegative(int value)
{
    if (value < 0)
    {
        throw new ArgumentOutOfRangeException(nameof(value));
    }
}

Para recuperar errores en Rust, retorna una variante de Ok o de Err desde un método:

fn error_if_negative(value: i32) -> Result<(), &'static str> {
    if value < 0 {
        Err("El argumento especificado esta fuera del rango de valores validos. (Parámetro 'value')")
    } else {
        Ok(())
    }
}

La macro panic! crea errores irrecuperables:

fn panic_if_negative(value: i32) {
    if value < 0 {
        panic!("El argumento especificado esta fuera del rango de valores validos. (Parámetro 'value')")
    }
}

Propagación de error

En .Net, excepciones son pasadas hacia arriba hasta que son tratadas o el programa termina. En Rust, los errores irrecuperables son similares, pero tratarlos es poco común.

Los errores recuperables, sin embargo necesitan ser propagados y tratarlos explícitamente. Están presentes siempre representados en la firma de funciones o métodos en Rust. Capturar las excepciones te permiten tomar acciones basadas en la presencia o ausencia de errores en C#.

void Write()
{
    try
    {
        File.WriteAllText("file.txt", "content");
    }
    catch (IOException)
    {
        Console.WriteLine("Escribiendo el archivo fallo.");
    }
}

En Rust, esto es un equivalente aproximado:

fn write() {
    match std::fs::File::create("temp.txt")
        .and_then(|mut file| std::io::Write::write_all(&mut file, b"content"))
    {
        Ok(_) => {}
        Err(_) => println!("Escribiendo el archivo fallo."),
    };
}

Frecuentemente, los errores recuperables necesitan ser propagados en lugar de ser tratados. Para esto, la firma del metodo necesita ser compatible con el tipo de error propagado. El operador ? propaga errores ergonómicamente:

fn write() -> Result<(), std::io::Error> {
    let mut file = std::fs::File::create("file.txt")?;
    std::io::Write::write_all(&mut file, b"content")?;
    Ok(())
}

Nota: Para propagar un error con el question mark operator la implementación del error necesita ser compatible, como describimos en un atajo para la propagación de errores. El tipo más "compatible" es el trait object error Box<dyn Error>.

Stack traces

Lanzar una excepción no capturada en .Net causara que el runtime imprima un stack trace que permitirá depurar el problema con contexto adicional.

Para errores irrecuperables en Rust, los backtraces de panic! ofrecen un comportamiento similar.

Los errores recuperables en Rust estable no soportan aún los backtraces, pero es soportado en Rust experimental cuando usamos el método provide.