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.
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.
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.
Option es algo más sencilla de usar.
En Result hay que indicar el tipo del valor correcto y del valor de error que se comunique.
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.
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.
La macro try! es tan usada dentro de Rust que dispone de azúcar sintáctico, el símbolo de interrogación ?.
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.
Existen muchas más opciones, como and, expect o iterar sobre un Option/Result.
Si nos gustan los crasheos (!) podemos forzarlos con la macro panic!. Crashea el programa con el mensaje que indiquemos
Y con esto hemos visto lo suficiente del manejo de errores en Rust.
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.