Gestione errores recuperables con Result

RustBeginner
Practicar Ahora

Introducción

Algunos fallos son errores de programación, pero otros se esperan durante la ejecución: una persona puede introducir texto con un formato incorrecto o un valor fuera del intervalo permitido. Rust representa estos resultados recuperables con Result<T, E>, que puede ser Ok(T) o Err(E).

En este laboratorio analizará texto válido y no válido sin provocar un pánico, devolverá errores de validación útiles y utilizará el operador ? para pasar los fallos al código que llama a la función. El proyecto de Cargo preparado mantiene cada modificación centrada en la gestión de errores.

Haga una coincidencia con un Result correcto

En este paso, analizará texto como un entero y hará una coincidencia con el resultado correcto.

El proyecto se encuentra en /home/labex/project/result-workshop, y src/main.rs contiene cuatro marcadores de posición específicos. Entre en el proyecto y abra el código fuente:

cd /home/labex/project/result-workshop
nano src/main.rs

Sustituya el comentario del paso 1 por:

    let valid = "42".parse::<i32>();
    match valid {
        Ok(value) => println!("Parsed value: {value}"),
        Err(error) => println!("Parse error: {error}"),
    }

parse es un método reutilizable que puede producir distintos tipos de salida. La parte ::<i32> proporciona el tipo de salida para esta llamada y solicita a Rust que interprete la cadena como un entero con signo de 32 bits. Este argumento de tipo entre corchetes angulares es distinto de una ruta como Message::Text, aunque ambos utilizan ::. El análisis puede fallar, por lo que el tipo devuelto es un Result. Ok(value) contiene el entero analizado y Err(error) contiene información sobre el motivo del fallo. Un match debe gestionar ambas variantes.

Guarde el archivo con Ctrl+O, pulse Enter y salga con Ctrl+X. Compruebe el código y ejecútelo:

cargo check
cargo run --quiet
Parsed value: 42

Solo se ejecutó la rama Ok, porque "42" es texto válido para un entero.

Recupérese de una entrada no válida

En este paso, gestionará el texto con formato incorrecto como datos, en lugar de permitir que bloquee el programa.

Un pánico indica que el programa alcanzó un estado que su diseño no esperaba y normalmente detiene la ejecución. La entrada externa con un formato incorrecto es una situación esperable, por lo que debe permanecer como un Err que el programa pueda inspeccionar y mostrar. Abra el código fuente:

nano src/main.rs

Sustituya el comentario del paso 2 por:

    let invalid = "many".parse::<i32>();
    match invalid {
        Ok(value) => println!("Unexpected value: {value}"),
        Err(error) => println!("Handled invalid input: {error}"),
    }

Esta coincidencia sigue cubriendo ambos resultados posibles, pero el texto preparado selecciona la rama Err. Guarde el archivo, salga y ejecute:

cargo run --quiet

La segunda línea debe ser:

Handled invalid input: invalid digit found in string

El programa continuó con normalidad y convirtió el error del analizador en un mensaje útil. Evite unwrap() con datos externos: unwrap() provocaría un pánico ante este mismo Err, en lugar de permitirle recuperarse.

Analice y valide con el operador de interrogación

En este paso, creará una función que pueda devolver un porcentaje o un mensaje de error útil.

El tipo Result<u32, String> significa que el éxito contiene un entero sin signo y el fallo contiene texto de error propiedad de la función. El operador ? extrae el valor de Ok; si encuentra un Err, devuelve inmediatamente ese error desde la función actual.

Abra el código fuente:

nano src/main.rs

Añada esta función encima de main:

fn parse_percentage(text: &str) -> Result<u32, String> {
    let value = text
        .parse::<u32>()
        .map_err(|_| format!("not a whole number: {text}"))?;

    if value <= 100 {
        Ok(value)
    } else {
        Err(format!("outside 0..=100: {value}"))
    }
}

Lea la función como una secuencia breve. Primero, parse::<u32>() intenta convertir el texto en un número entero. Después, map_err(...) cambia únicamente un fallo de análisis por un String más claro que contiene la entrada original.

A continuación, ? extrae el número correcto para asignarlo a value o devuelve inmediatamente ese nuevo error desde parse_percentage. Si el análisis tiene éxito, if valida el número y devuelve Ok(value) o un Err que indica que el valor está fuera del intervalo.

El parámetro _ de la clausura indica que el valor original del error del analizador se ignora deliberadamente, porque el nuevo mensaje lo sustituye. Aquí _ es un parámetro ignorado; anteriormente, _ => era una rama comodín de match y let _ = descartaba un resultado completo.

Sustituya el comentario del paso 3 dentro de main por:

    println!("85 => {:?}", parse_percentage("85"));
    println!("150 => {:?}", parse_percentage("150"));
    println!("many => {:?}", parse_percentage("many"));

El formateador :? muestra la variante Ok o Err para este experimento de aprendizaje. Guarde el archivo, salga y compruebe el código y ejecútelo:

cargo check
cargo run --quiet

Las nuevas líneas deben ser:

85 => Ok(85)
150 => Err("outside 0..=100: 150")
many => Err("not a whole number: many")

Ahora la función distingue entre un valor válido, un valor analizado pero fuera del intervalo y un texto con formato incorrecto.

Propague un error a través de otra función

En este paso, utilizará ? en el límite de una segunda función y gestionará el resultado final en main.

Una función auxiliar no siempre sabe cómo debe mostrar o recuperar un error la aplicación. Puede propagar el Err hacia arriba y dejar que quien la llama tome la decisión. Abra el código fuente:

nano src/main.rs

Añada esta función entre parse_percentage y main:

fn acceptance_message(text: &str) -> Result<String, String> {
    let value = parse_percentage(text)?;
    Ok(format!("accepted {value}%"))
}

Si el análisis o la validación fallan, ? devuelve inmediatamente el String de error existente. Si tienen éxito, la función incluye su mensaje en Ok.

Sustituya el comentario del paso 4 dentro de main por:

    for text in ["73", "bad"] {
        match acceptance_message(text) {
            Ok(message) => println!("{text} => {message}"),
            Err(message) => println!("{text} => error: {message}"),
        }
    }

El bucle proporciona una entrada correcta y otra que provoca un fallo. main es el límite que convierte cada resultado en una salida visible para el usuario. Guarde el archivo, salga y compruebe el código y ejecútelo:

cargo check
cargo run --quiet

Las líneas finales deben ser:

73 => accepted 73%
bad => error: not a whole number: bad

El mismo contexto de error creado por parse_percentage se conservó al propagarse a través de acceptance_message.

Resumen

Hizo coincidencias con valores Result correctos y fallidos, trató la entrada con formato incorrecto como una condición recuperable, creó errores String con contexto mediante map_err y propagó los fallos a través de los límites de las funciones con ?, en lugar de provocar un pánico con unwrap.