Adrianistán

Gestión de errores en Rust, Option y Result

03/07/2017
¿Conoces el error del billón de dólares? Tony Hoare es un reputado estudioso de la ciencia de la computación. Cuando se encontraba diseñando ALGOL W, se le ocurrió incorporar la referencia a NULL. Sin embargo, los errores informáticos que ha propiciado su existencia han supuesto pérdidas económicas superiores al billón de dólares. Es por ello que él mismo denominó a su creación, el error del billón de dólares.

El uso de NULL puede traer muchas consecuencias negativas, pero como el propio Hoare dijo: era tan fácil de implementar que no pude resistirme. Además, usarlo es fácil. Existen lenguajes sin NULL desde hace tiempo pero su uso no es muy frecuente y ciertamente, añaden complejidad a la programación. Rust soluciona esto con dos objetos muy sencillos de utilizar, Option y Result.

Option y Result


Ambos son objetos que sirven para llevar cualquier otro valor. Hemos de desempapelarlos para sacar el verdadero valor, si es que existe. Option en realidad es un enum con dos posibles valores: Some y None. Result también es un enum con dos posibles valores: Ok y Err.

¿Cuándo usar Option y cuándo usar Result?


Esta duda es muy frecuente ya que funcionan de manera muy similar. La regla general es que Result tiene que usarse con errores y si no se tratan, cerrar el programa, mientras que Option lleva cosas que pueden estar o no, pero su inexistencia no conlleva un error.

Uso


Option es algo más sencilla de usar.


let opt: Option<i32> = Some(42);
let opt: Option<i32> = None;


En Result hay que indicar el tipo del valor correcto y del valor de error que se comunique.


let operacion_peligrosa: Result<i32,String> = Ok(42);
let operacion_peligrosa: Result<i32,String> = Err(String::from("La operación ha fallado"));


if-let


Option y Result pueden integrarse con estructuras de control para un manejo idomático de estas situaciones. Un ejemplo es if-let. Imagina un perfil de una aplicación web. Hay campos obligatorios, como usuario y contraseña, pero otros no, como la página web.


struct Perfil{
username: String,
password: String,
url: Option<String>
}

impl Perfil{
fn new(u: String, p: String) -> Self{
Perfil {username: u, password: p, url: None}
}
}

fn main(){
let mut p1 = Perfil::new(String::from("aarroyoc"),String::from("1234"));
let mut p2 = Perfil::new(String::from("The42"),String::from("incorrect"));

p1.url = Some(String::from("https://blog.adrianistan.eu"));

for perfil in [p1,p2].iter() {
let url = perfil.url.clone();
if let Some(url) = url{
println!("URL: {}",url);
}
}

}


try!


try! es una macro que permite manejar con mayor claridad las operaciones que puedan generar muchos errores. try! solo se puede usar en funciones que devuelvan Result. Básicamente, try! devuelve inmediatamente el Err correspondiente si la operación resultante no ha sido exitosa, permitiendo una salida antes de tiempo y un código mucho más limpio.


fn peligro() -> Result<i32,String>{
Err(String::from("Operación inválida"))
}

fn funcion_error() -> Result<i32,String>{
let n = try!(peligro()); // aquí ya se sale de la función
Ok(n)
}

fn main(){
let n = funcion_error().unwrap();

}


La macro try! es tan usada dentro de Rust que dispone de azúcar sintáctico, el símbolo de interrogación ?.


fn peligro() -> Result<i32,String>{
Err(String::from("Operación inválida"))
}

fn funcion_error() -> Result<i32,String>{
let n = peligro()?;
Ok(n)
}

fn main(){
let n = funcion_error().unwrap();
}


 

Unwrap


Vamos a observar las operaciones de desempapelado que existen para Option y Result. La más básica es unwrap. Intenta desempapelar el valor, si resulta que el Result/Option era un Err/None, el programa crashea.

unwrap_or intenta desempapelar y si el valor no existe, se sustituye por el valor indicado en el or.

unwrap_or_else es similar a unwrap_or pero toma una función como parámetro. Así es posible crear una cadena de intentos.


fn main(){
// let n = function_error().unwrap(); crash
let n = function_error().unwrap_or(7);
let n = funcion_error().unwrap_or_else(|t|{
7
});
println!("{}",n);
}


Existen muchas más opciones, como and, expect o iterar sobre un Option/Result.

panic!


Si nos gustan los crasheos (!) podemos forzarlos con la macro panic!. Crashea el programa con el mensaje que indiquemos


fn main(){
panic!("Adiós muy buenas");
}


Y con esto hemos visto lo suficiente del manejo de errores en Rust.
Tags: option programacion rust tutorial errores crash result