sábado, 13 de abril de 2019

Creando videojuegos en Rust lang (Parte 19)

Programa 13. "pre_hello_ggez13" Manejando excepciones y salidas de funciones: "Result"
Al usar librerías o funciones tenemos situaciones donde puede haber un error interno en la librería. En estas situaciones sería ideal que nos regresen un error describiendo la naturaleza de la falla. Podríamos usar "Option" como se vio en la sección pasada, sin embargo Option sólo nos deja definir un tipo de dato. Esta limitación se vuelve un problema si esperamos que el error sea un String pero el valor normal (sin error) es otro tipo de dato. Por ejemplo si esperamos que el valor de retorno es un entero mientras que el mensaje de error es un String. Para estas situaciones usamos el enum "Result" que incluye Rust de forma predeterminada.

El enum "Result" de Rust tiene la siguiente definición que permite dos tipos de datos genéricos <T, E>:

1
2
3
4
enum Result<T, E> {
   Ok(T),
   Err(E),
}

Como se puede observar puede tomar como entrada dos tipos diferentes de datos "T" y "E". La "E" es para el tipo de datos del error. Y la "T" es para el tipo de dato del resultado correcto, parecido a Option. Y los nombres de los contenedores de T y E son "Ok" y "Err". Result es usado por muchas funciones de la librería estándar "std" de Rust.

¿Cómo extraemos los datos dentro de un Result? De forma similar a cuando usamos Option, hay varias opciones para extraer los valores internos de un Result:
  • Usar "match". Similar a como se manejan los "enum".
  • Usar el método de "Result" llamado ".unwrap()". Funciona de forma similar a su uso con  Option. Si el valor es Ok entonces regresa el valor interno T. Si el valor es Err entonces entra en pánico y detiene la ejecución del programa. 
  • Usar el método de "Result" lamado ".unwrap_or(default)". Funciona de forma similar a su uso con Option. Si el valor es Ok entonces regresa el valor interno T. Si el valor es Err entonces regresa el valor "default" que tiene que ser del mismo tipo T. 
  • Usar el método de "Result" llamado ".expect("mensaje de error")". Funciona de forma similar a su uso con Option. Si el valor es Ok entonces regresa el valor interno T. Si el valor es Err entonces entra en pánico y regresa el "mensaje de error" y intenta imprimir el tipo E después del mensaje de error. 
  • Si estamos dentro de una función que regresa "Result" y tenemos múltiples llamadas que regresan "Result" podemos usar el operador "?" para simplificar la lógica de regresar Err. Si ponemos "?" al final de una línea esta regresará el valor de Ok desenvuelto si todo salió bien. Por otra parte si hubo un error "Err" como salida entonces iniciará una salida prematura en nuestra función. Es decir, nos ahorra hacer múltiples bloques "match" o bloques "if" para cada llamada y nos permite regresar Error "Err" inmediatamente. Es por esta razón que este operador "?" sólo funciona dentro de una función que regresa Result.
También tenemos unwrap_err() and expect_err() que tienen el comportamiento opuesto a unwrap() y expect(). Es decir, intentan regresar E o en otro caso entran en pánico con Ok y regresan el valor interno de Ok como parte del mensaje de error. Otra opción es usar los métodos "ok()" o "err()", estos métodos de "Result" sirven para convertir el Result a un Option. Finalmente, si lo necesitamos, hay un método de "Result" llamado ".is_ok()" que regresa un booleano (verdadero o falso) que nos dice si es Ok o no. De forma contraria hay otro método de "Result" llamado ".is_err()" y este valor es un booleano (verdadero o falso) que nos dice si es Err o no.

Todo esto se muestra a continuación:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
fn checar_division(dividendo: i32, divisor: i32) -> Result<i32,String> {
    if divisor == 0 {
        Err(String::from("División entre cero"))
    } else {
        Ok(dividendo/divisor)
    }
}

fn checar_resultado(entrada: &Result<i32,String>) {
    match entrada {
        Result::Err(texto) => println!("Error, mensaje: {}", texto),
        Result::Ok(var) => println!("Resultado: {}", var)
    }
}

fn procesar_div(dividendo: i32, 
                divisor: i32) -> Result<i32,String> {
    println!("Primera división: {}/{}", dividendo, divisor);
    let cociente1 = checar_division(dividendo, divisor)?;
    println!("Segunda división: {}/{}", cociente1, divisor-1);
    let cociente2 = checar_division(cociente1, divisor-1)?;
    println!("Tercera división: {}/{}", cociente2, divisor-2);
    let cociente3 = checar_division(cociente2, divisor-2)?;
    Result::Ok(cociente3)
}

fn main() {
    let x: Result<i32, String> = checar_division(6,3);
    let y: Result<i32, String> = checar_division(0,0);
    println!("x.is_ok() = {}", x.is_ok());
    println!("y.is_ok() = {}", y.is_ok());
    println!("x.is_err() = {}", x.is_err());
    println!("y.is_err() = {}", y.is_err());
    checar_resultado(&x);
    checar_resultado(&y);
    println!("procesar_div(6,0) = {:?}",procesar_div(6, 0));
    println!("procesar_div(6,1) = {:?}",procesar_div(6, 1));
    println!("procesar_div(6,2) = {:?}",procesar_div(6, 2));
    println!("procesar_div(6,3) = {:?}",procesar_div(6, 3));
    println!("x.unwrap() = {}", x.unwrap());
    println!("No podemos usar y.unwrap porque detendría la ejecución");
    // Reasignando los resultados ya que unwrap() mueve el valor
    let x: Result<i32, String> = checar_division(6,3);
    println!("x.unwrap_or(0) = {}", x.unwrap_or(0));
    println!("y.unwrap_or(0) = {}", y.unwrap_or(0));
    // Reasignando los resultados ya que unwrap_or() mueve el valor
    let x: Result<i32, String> = checar_division(6,3);
    let y: Result<i32, String> = checar_division(0,0);
    println!("x.ok() = {:?}", x.ok());
    println!("y.ok() = {:?}", y.ok());
    // Reasignando los resultados ya que ok() mueve el valor
    let x: Result<i32, String> = checar_division(6,3);
    let y: Result<i32, String> = checar_division(0,0);
    println!("x.err() = {:?}", x.err());
    println!("y.err() = {:?}", y.err());
    // Reasignando los resultados ya que err() mueve el valor
    let x: Result<i32, String> = checar_division(6,3);
    let y: Result<i32, String> = checar_division(0,0);
    println!("Sin x.unwrap_err ya que detiene la ejecución");
    println!("y.unwrap_err() = {:?}", y.unwrap_err());
    // Reasignando 'y' ya que unwrap_err mueve el valor
    let y: Result<i32, String> = checar_division(0,0);
    println!("Sin x.expect_err ya que detiene la ejecución, y da el valor");
    println!("y.expect_err() = {:?}", y.expect_err("Esperando error"));
    // Reasignando 'y' ya que expect_err mueve el valor
    let y: Result<i32, String> = checar_division(0,0);
    println!("x.expect(mensaje) = {:?}", x.expect("Intentando dividir x"));
    // La siguiente línea interrumpe la ejecución ya que es un Err:
    println!("y.expect(mensaje) = {:?}", y.expect("Intentando dividir y"));
}

Este es el resultado de correr el código:


Notamos como la última línea interrumpe la ejecución por ser un .expect() llamado a un Err. Y vemos en la captura como nos muestra el mensaje de nuestro expect "Intentando dividir y" además que el mensaje de error interno "División entre cero".

Navegación:
Primera parte
Siguiente parte
Parte anterior

Fuente (inglés):

No hay comentarios.:

Publicar un comentario