Errores Recuperables con Result

RustRustBeginner
Practicar Ahora

This tutorial is from open-source community. Access the source code

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

Introducción

Bienvenido a Recoverable Errors With Result. Esta práctica es parte del Rust Book. Puedes practicar tus habilidades de Rust en LabEx.

En esta práctica, aprenderemos a manejar errores recuperables con el enum Result en Rust, que nos permite interpretar y responder a errores sin terminar el programa.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL rust(("Rust")) -.-> rust/ErrorHandlingandDebuggingGroup(["Error Handling and Debugging"]) rust(("Rust")) -.-> rust/AdvancedTopicsGroup(["Advanced Topics"]) rust(("Rust")) -.-> rust/BasicConceptsGroup(["Basic Concepts"]) rust(("Rust")) -.-> rust/DataTypesGroup(["Data Types"]) rust(("Rust")) -.-> rust/FunctionsandClosuresGroup(["Functions and Closures"]) rust(("Rust")) -.-> rust/DataStructuresandEnumsGroup(["Data Structures and Enums"]) rust/BasicConceptsGroup -.-> rust/variable_declarations("Variable Declarations") rust/BasicConceptsGroup -.-> rust/mutable_variables("Mutable Variables") rust/DataTypesGroup -.-> rust/string_type("String Type") rust/FunctionsandClosuresGroup -.-> rust/function_syntax("Function Syntax") rust/FunctionsandClosuresGroup -.-> rust/expressions_statements("Expressions and Statements") rust/DataStructuresandEnumsGroup -.-> rust/method_syntax("Method Syntax") rust/ErrorHandlingandDebuggingGroup -.-> rust/panic_usage("panic! Usage") rust/AdvancedTopicsGroup -.-> rust/operator_overloading("Traits for Operator Overloading") subgraph Lab Skills rust/variable_declarations -.-> lab-100410{{"Errores Recuperables con Result"}} rust/mutable_variables -.-> lab-100410{{"Errores Recuperables con Result"}} rust/string_type -.-> lab-100410{{"Errores Recuperables con Result"}} rust/function_syntax -.-> lab-100410{{"Errores Recuperables con Result"}} rust/expressions_statements -.-> lab-100410{{"Errores Recuperables con Result"}} rust/method_syntax -.-> lab-100410{{"Errores Recuperables con Result"}} rust/panic_usage -.-> lab-100410{{"Errores Recuperables con Result"}} rust/operator_overloading -.-> lab-100410{{"Errores Recuperables con Result"}} end

Errores recuperables con Result

La mayoría de los errores no son lo suficientemente graves como para que el programa se detenga por completo. A veces, cuando una función falla, es por un motivo que se puede interpretar y responder fácilmente. Por ejemplo, si intentas abrir un archivo y esa operación falla porque el archivo no existe, es posible que desees crear el archivo en lugar de terminar el proceso.

Recuerda de "Manejar el fracaso potencial con Result" que el enum Result está definido como tener dos variantes, Ok y Err, de la siguiente manera:

enum Result<T, E> {
    Ok(T),
    Err(E),
}

Los parámetros de tipo genéricos T y E: discutiremos los genéricos con más detalle en el Capítulo 10. Lo que debes saber en este momento es que T representa el tipo del valor que se devolverá en un caso de éxito dentro de la variante Ok, y E representa el tipo del error que se devolverá en un caso de fracaso dentro de la variante Err. Debido a que Result tiene estos parámetros de tipo genéricos, podemos usar el tipo Result y las funciones definidas en él en muchas situaciones diferentes donde el valor de éxito y el valor de error que queremos devolver pueden variar.

Llamemos a una función que devuelve un valor Result porque la función podría fallar. En la Lista 9-3 intentamos abrir un archivo.

Nombre del archivo: src/main.rs

use std::fs::File;

fn main() {
    let greeting_file_result = File::open("hello.txt");
}

Lista 9-3: Abriendo un archivo

El tipo de retorno de File::open es un Result<T, E>. El parámetro genérico T ha sido rellenado por la implementación de File::open con el tipo del valor de éxito, std::fs::File, que es un manejador de archivo. El tipo de E usado en el valor de error es std::io::Error. Este tipo de retorno significa que la llamada a File::open podría tener éxito y devolver un manejador de archivo que podemos leer o escribir. La llamada a la función también podría fallar: por ejemplo, el archivo podría no existir, o no podríamos tener permiso para acceder al archivo. La función File::open necesita tener una manera de decirnos si tuvo éxito o fracasó y, al mismo tiempo, darnos ya sea el manejador de archivo o la información de error. Esta información es exactamente lo que el enum Result transmite.

En el caso en el que File::open tiene éxito, el valor en la variable greeting_file_result será una instancia de Ok que contiene un manejador de archivo. En el caso en el que falla, el valor en greeting_file_result será una instancia de Err que contiene más información sobre el tipo de error que ocurrió.

Necesitamos agregar al código de la Lista 9-3 para tomar diferentes acciones dependiendo del valor que devuelve File::open. La Lista 9-4 muestra una manera de manejar el Result usando una herramienta básica, la expresión match que discutimos en el Capítulo 6.

Nombre del archivo: src/main.rs

use std::fs::File;

fn main() {
    let greeting_file_result = File::open("hello.txt");

    let greeting_file = match greeting_file_result {
        Ok(file) => file,
        Err(error) => {
            panic!("Problem opening the file: {:?}", error);
        }
    };
}

Lista 9-4: Usando una expresión match para manejar las variantes Result que podrían ser devueltas

Tenga en cuenta que, al igual que el enum Option, el enum Result y sus variantes han sido traídos al ámbito por el preludio, por lo que no necesitamos especificar Result:: antes de las variantes Ok y Err en los brazos de la match.

Cuando el resultado es Ok, este código devolverá el valor interno file de la variante Ok, y luego asignamos ese valor de manejador de archivo a la variable greeting_file. Después de la match, podemos usar el manejador de archivo para leer o escribir.

El otro brazo de la match maneja el caso en el que obtenemos un valor Err de File::open. En este ejemplo, hemos elegido llamar a la macro panic!. Si no hay un archivo llamado hello.txt en nuestro directorio actual y ejecutamos este código, veremos la siguiente salida de la macro panic!:

thread 'main' panicked at 'Problem opening the file: Os { code:
 2, kind: NotFound, message: "No such file or directory" }',
src/main.rs:8:23

Como de costumbre, esta salida nos dice exactamente lo que ha salido mal.

Coincidir con diferentes errores

El código de la Lista 9-4 hará panic! sin importar por qué falló File::open. Sin embargo, queremos tomar diferentes acciones para diferentes razones de falla. Si File::open falla porque el archivo no existe, queremos crear el archivo y devolver el manejador al nuevo archivo. Si File::open falla por cualquier otra razón, por ejemplo, porque no tenemos permiso para abrir el archivo, todavía queremos que el código haga panic! de la misma manera que lo hizo en la Lista 9-4. Para esto, agregamos una expresión match interna, como se muestra en la Lista 9-5.

Nombre del archivo: src/main.rs

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let greeting_file_result = File::open("hello.txt");

    let greeting_file = match greeting_file_result {
        Ok(file) => file,
        Err(error) => match error.kind() {
            ErrorKind::NotFound => {
                match File::create("hello.txt") {
                    Ok(fc) => fc,
                    Err(e) => panic!(
                        "Problem creating the file: {:?}",
                        e
                    ),
                }
            }
            other_error => {
                panic!(
                    "Problem opening the file: {:?}",
                    other_error
                );
            }
        },
    };
}

Lista 9-5: Manejar diferentes tipos de errores de diferentes maneras

El tipo del valor que devuelve File::open dentro de la variante Err es io::Error, que es un struct proporcionado por la biblioteca estándar. Este struct tiene un método kind que podemos llamar para obtener un valor io::ErrorKind. El enum io::ErrorKind es proporcionado por la biblioteca estándar y tiene variantes que representan los diferentes tipos de errores que pueden resultar de una operación io. La variante que queremos usar es ErrorKind::NotFound, que indica que el archivo que estamos intentando abrir todavía no existe. Entonces coincidimos en greeting_file_result, pero también tenemos una coincidencia interna en error.kind().

La condición que queremos comprobar en la coincidencia interna es si el valor devuelto por error.kind() es la variante NotFound del enum ErrorKind. Si es así, intentamos crear el archivo con File::create. Sin embargo, debido a que File::create también podría fallar, necesitamos un segundo brazo en la expresión match interna. Cuando el archivo no se puede crear, se imprime un mensaje de error diferente. El segundo brazo de la match externa permanece igual, por lo que el programa se detiene en cualquier error aparte del error de archivo faltante.

Alternativas a usar match con Result<T, E>

¡Eso es mucho match! La expresión match es muy útil pero también muy primitiva. En el Capítulo 13, aprenderás sobre las closures, que se usan con muchos de los métodos definidos en Result<T, E>. Estos métodos pueden ser más concisos que usar match al manejar valores de Result<T, E> en tu código.

Por ejemplo, aquí hay otra forma de escribir la misma lógica que se muestra en la Lista 9-5, esta vez usando closures y el método unwrap_or_else:

// src/main.rs
use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let greeting_file = File::open("hello.txt").unwrap_or_else(|error| {
        if error.kind() == ErrorKind::NotFound {
            File::create("hello.txt").unwrap_or_else(|error| {
                panic!("Problem creating the file: {:?}", error);
            })
        } else {
            panic!("Problem opening the file: {:?}", error);
        }
    });
}

Aunque este código tiene el mismo comportamiento que la Lista 9-5, no contiene ninguna expresión match y es más limpio de leer. Vuelve a este ejemplo después de haber leído el Capítulo 13 y consulta el método unwrap_or_else en la documentación de la biblioteca estándar. Muchos más de estos métodos pueden limpiar expresiones match anidadas enormes cuando estás manejando errores.

Atajos para panic en caso de error: unwrap y expect

Usar match funciona bastante bien, pero puede ser un poco verboso y no siempre comunica bien la intención. El tipo Result<T, E> tiene muchos métodos auxiliares definidos en él para realizar varias tareas más específicas. El método unwrap es un método atajo implementado de la misma manera que la expresión match que escribimos en la Lista 9-4. Si el valor de Result es la variante Ok, unwrap devolverá el valor dentro de Ok. Si el Result es la variante Err, unwrap llamará a la macro panic! por nosotros. Aquí hay un ejemplo de unwrap en acción:

Nombre del archivo: src/main.rs

use std::fs::File;

fn main() {
    let greeting_file = File::open("hello.txt").unwrap();
}

Si ejecutamos este código sin un archivo hello.txt, veremos un mensaje de error de la llamada a panic! que hace el método unwrap:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os {
code: 2, kind: NotFound, message: "No such file or directory" }',
src/main.rs:4:49

Del mismo modo, el método expect nos permite también elegir el mensaje de error de panic!. Usar expect en lugar de unwrap y proporcionar buenos mensajes de error puede transmitir tu intención y facilitar la búsqueda de la fuente de un panic. La sintaxis de expect es la siguiente:

Nombre del archivo: src/main.rs

use std::fs::File;

fn main() {
    let greeting_file = File::open("hello.txt")
     .expect("hello.txt should be included in this project");
}

Usamos expect de la misma manera que unwrap: para devolver el manejador de archivo o llamar a la macro panic!. El mensaje de error usado por expect en su llamada a panic! será el parámetro que le pasamos a expect, en lugar del mensaje predeterminado de panic! que usa unwrap. Aquí es como se ve:

thread 'main' panicked at 'hello.txt should be included in this project: Os {
code: 2, kind: NotFound, message: "No such file or directory" }',
src/main.rs:5:10

En código de calidad de producción, la mayoría de los rustaceos eligen expect en lugar de unwrap y dan más contexto sobre por qué se espera que la operación siempre tenga éxito. De esa manera, si tus suposiciones alguna vez resultan ser erróneas, tienes más información para usar en la depuración.

Propagación de errores

Cuando la implementación de una función llama a algo que podría fallar, en lugar de manejar el error dentro de la función misma, puedes devolver el error al código que la llama para que pueda decidir qué hacer. Esto se conoce como propagar el error y le da más control al código que llama, donde puede haber más información o lógica que dicta cómo se debe manejar el error que la que tienes disponible en el contexto de tu código.

Por ejemplo, la Lista 9-6 muestra una función que lee un nombre de usuario de un archivo. Si el archivo no existe o no se puede leer, esta función devolverá esos errores al código que llamó a la función.

Nombre del archivo: src/main.rs

use std::fs::File;
use std::io::{self, Read};

1 fn read_username_from_file() -> Result<String, io::Error> {
  2 let username_file_result = File::open("hello.txt");

  3 let mut username_file = match username_file_result {
      4 Ok(file) => file,
      5 Err(e) => return Err(e),
    };

  6 let mut username = String::new();

  7 match username_file.read_to_string(&mut username) {
      8 Ok(_) => Ok(username),
      9 Err(e) => Err(e),
    }
}

Lista 9-6: Una función que devuelve errores al código que llama usando match

Esta función se puede escribir de manera mucho más corta, pero vamos a empezar haciéndola manualmente para explorar el manejo de errores; al final, mostraremos la forma más corta. Veamos primero el tipo de retorno de la función: Result<String, io::Error> [1]. Esto significa que la función está devolviendo un valor del tipo Result<T, E>, donde el parámetro genérico T ha sido rellenado con el tipo concreto String y el tipo genérico E ha sido rellenado con el tipo concreto io::Error.

Si esta función tiene éxito sin problemas, el código que llama a esta función recibirá un valor Ok que contiene un String - el username que esta función leyó del archivo [8]. Si esta función encuentra algún problema, el código que llama recibirá un valor Err que contiene una instancia de io::Error que contiene más información sobre cuáles fueron los problemas. Elegimos io::Error como el tipo de retorno de esta función porque ese es el tipo del valor de error devuelto por ambas operaciones que estamos llamando en el cuerpo de esta función que podrían fallar: la función File::open [2] y el método read_to_string [7].

El cuerpo de la función comienza llamando a la función File::open [2]. Luego manejamos el valor de Result con un match similar al match de la Lista 9-4. Si File::open tiene éxito, el manejador de archivo en la variable de patrón file [4] se convierte en el valor de la variable mutable username_file [3] y la función continúa. En el caso Err, en lugar de llamar a panic!, usamos la palabra clave return para salir temprano de la función por completo y pasar el valor de error de File::open, ahora en la variable de patrón e, de vuelta al código que llama como el valor de error de esta función [5].

Entonces, si tenemos un manejador de archivo en username_file, la función luego crea un nuevo String en la variable username [6] y llama al método read_to_string en el manejador de archivo en username_file para leer el contenido del archivo en username [7]. El método read_to_string también devuelve un Result porque podría fallar, aunque File::open tuvo éxito. Entonces necesitamos otro match para manejar ese Result: si read_to_string tiene éxito, entonces nuestra función ha tenido éxito, y devolvemos el nombre de usuario del archivo que ahora está en username envuelto en un Ok. Si read_to_string falla, devolvemos el valor de error de la misma manera que devolvemos el valor de error en el match que manejó el valor de retorno de File::open. Sin embargo, no necesitamos decir explícitamente return, porque esta es la última expresión en la función [9].

El código que llama a este código luego manejará recibir ya sea un valor Ok que contiene un nombre de usuario o un valor Err que contiene un io::Error. Es responsabilidad del código que llama decidir qué hacer con esos valores. Si el código que llama recibe un valor Err, podría llamar a panic! y detener el programa, usar un nombre de usuario predeterminado o buscar el nombre de usuario en algún lugar diferente al archivo, por ejemplo. No tenemos suficiente información sobre lo que el código que llama está realmente intentando hacer, así que propagamos toda la información de éxito o error hacia arriba para que la maneje adecuadamente.

Este patrón de propagación de errores es tan común en Rust que Rust proporciona el operador de interrogación ? para hacerlo más fácil.

Un atajo para la propagación de errores: el operador?

La Lista 9-7 muestra una implementación de read_username_from_file que tiene la misma funcionalidad que la de la Lista 9-6, pero esta implementación utiliza el operador ?.

Nombre del archivo: src/main.rs

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    let mut username_file = File::open("hello.txt")?;
    let mut username = String::new();
    username_file.read_to_string(&mut username)?;
    Ok(username)
}

Lista 9-7: Una función que devuelve errores al código que llama usando el operador ?

El ? colocado después de un valor de Result está definido para funcionar de manera casi igual que las expresiones match que definimos para manejar los valores de Result en la Lista 9-6. Si el valor del Result es un Ok, el valor dentro del Ok se devolverá desde esta expresión y el programa continuará. Si el valor es un Err, el Err se devolverá desde la función completa como si hubiéramos usado la palabra clave return, de modo que el valor de error se propague al código que llama.

Hay una diferencia entre lo que hace la expresión match de la Lista 9-6 y lo que hace el operador ?: los valores de error en los que se llama al operador ? pasan por la función from, definida en el trato From de la biblioteca estándar, que se utiliza para convertir valores de un tipo a otro. Cuando el operador ? llama a la función from, el tipo de error recibido se convierte en el tipo de error definido en el tipo de retorno de la función actual. Esto es útil cuando una función devuelve un tipo de error para representar todas las formas en que una función puede fallar, incluso si partes pueden fallar por muchas razones diferentes.

Por ejemplo, podríamos cambiar la función read_username_from_file de la Lista 9-7 para devolver un tipo de error personalizado llamado OurError que definimos. Si también definimos impl From<io::Error> for OurError para construir una instancia de OurError a partir de un io::Error, entonces las llamadas al operador ? en el cuerpo de read_username_from_file llamarán a from y convertirán los tipos de error sin necesidad de agregar más código a la función.

En el contexto de la Lista 9-7, el ? al final de la llamada a File::open devolverá el valor dentro de un Ok a la variable username_file. Si se produce un error, el operador ? devolverá temprano de la función completa y dará cualquier valor Err al código que llama. Lo mismo se aplica al ? al final de la llamada a read_to_string.

El operador ? elimina mucha plantilla y hace que la implementación de esta función sea más simple. Incluso podríamos acortar este código aún más enlazando llamadas a métodos inmediatamente después del ?, como se muestra en la Lista 9-8.

Nombre del archivo: src/main.rs

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    let mut username = String::new();

    File::open("hello.txt")?.read_to_string(&mut username)?;

    Ok(username)
}

Lista 9-8: Enlazando llamadas a métodos después del operador ?

Hemos movido la creación del nuevo String en username al principio de la función; esa parte no ha cambiado. En lugar de crear una variable username_file, hemos enlazado la llamada a read_to_string directamente en el resultado de File::open("hello.txt")?. Todavía tenemos un ? al final de la llamada a read_to_string, y todavía devolvemos un valor Ok que contiene username cuando tanto File::open como read_to_string tienen éxito en lugar de devolver errores. La funcionalidad es nuevamente la misma que en la Lista 9-6 y la Lista 9-7; esta es solo una forma diferente y más cómoda de escribirlo.

La Lista 9-9 muestra una forma de hacerlo aún más corto usando fs::read_to_string.

Nombre del archivo: src/main.rs

use std::fs;
use std::io;

fn read_username_from_file() -> Result<String, io::Error> {
    fs::read_to_string("hello.txt")
}

Lista 9-9: Usando fs::read_to_string en lugar de abrir y luego leer el archivo

Leer un archivo en una cadena es una operación bastante común, por lo que la biblioteca estándar proporciona la función conveniente fs::read_to_string que abre el archivo, crea un nuevo String, lee el contenido del archivo, coloca el contenido en ese String y lo devuelve. Por supuesto, usar fs::read_to_string no nos da la oportunidad de explicar todo el manejo de errores, así que lo hicimos de la manera más larga primero.

Dónde se puede usar el operador?

El operador ? solo se puede usar en funciones cuyo tipo de retorno sea compatible con el valor en el que se usa el ?. Esto se debe a que el operador ? está definido para realizar una devolución temprana de un valor fuera de la función, de la misma manera que la expresión match que definimos en la Lista 9-6. En la Lista 9-6, la match estaba usando un valor de Result, y el brazo de retorno temprano devolvía un valor Err(e). El tipo de retorno de la función debe ser un Result para que sea compatible con esta return.

En la Lista 9-10, veamos el error que obtendremos si usamos el operador ? en una función main con un tipo de retorno que no es compatible con el tipo del valor en el que usamos ?.

Nombre del archivo: src/main.rs

use std::fs::File;

fn main() {
    let greeting_file = File::open("hello.txt")?;
}

Lista 9-10: Intentar usar el ? en la función main que devuelve () no se compilará.

Este código abre un archivo, lo que podría fallar. El operador ? sigue el valor de Result devuelto por File::open, pero esta función main tiene el tipo de retorno de (), no Result. Cuando compilamos este código, obtenemos el siguiente mensaje de error:

error[E0277]: the `?` operator can only be used in a function that returns
`Result` or `Option` (or another type that implements `FromResidual`)
 --> src/main.rs:4:48
  |
3 | / fn main() {
4 | |     let greeting_file = File::open("hello.txt")?;
  | |                                                ^ cannot use the `?`
operator in a function that returns `()`
5 | | }
  | |_- this function should return `Result` or `Option` to accept `?`
  |
  = help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not
implemented for `()`

Este error señala que solo se nos permite usar el operador ? en una función que devuelve Result, Option o otro tipo que implemente FromResidual.

Para corregir el error, tienes dos opciones. Una opción es cambiar el tipo de retorno de tu función para que sea compatible con el valor en el que estás usando el operador ?, siempre y cuando no tengas restricciones que lo impidan. La otra opción es usar una match o uno de los métodos de Result<T, E> para manejar el Result<T, E> de la manera que sea adecuada.

El mensaje de error también mencionó que ? se puede usar con valores de Option<T> también. Al igual que al usar ? en Result, solo se puede usar ? en Option en una función que devuelve un Option. El comportamiento del operador ? cuando se llama en un Option<T> es similar a su comportamiento cuando se llama en un Result<T, E>: si el valor es None, el None se devolverá temprano de la función en ese momento. Si el valor es Some, el valor dentro del Some es el valor resultante de la expresión, y la función continúa. La Lista 9-11 tiene un ejemplo de una función que encuentra el último carácter de la primera línea en el texto dado.

fn last_char_of_first_line(text: &str) -> Option<char> {
    text.lines().next()?.chars().last()
}

Lista 9-11: Usando el operador ? en un valor de Option<T>

Esta función devuelve Option<char> porque es posible que haya un carácter ahí, pero también es posible que no haya. Este código toma el argumento de rebanada de cadena text y llama al método lines en él, que devuelve un iterador sobre las líneas de la cadena. Debido a que esta función quiere examinar la primera línea, llama a next en el iterador para obtener el primer valor del iterador. Si text es la cadena vacía, esta llamada a next devolverá None, en cuyo caso usamos ? para detener y devolver None desde last_char_of_first_line. Si text no es la cadena vacía, next devolverá un valor Some que contiene una rebanada de cadena de la primera línea en text.

El ? extrae la rebanada de cadena, y podemos llamar a chars en esa rebanada de cadena para obtener un iterador de sus caracteres. Estamos interesados en el último carácter en esta primera línea, así que llamamos a last para devolver el último elemento del iterador. Esto es un Option porque es posible que la primera línea sea la cadena vacía; por ejemplo, si text empieza con una línea en blanco pero tiene caracteres en otras líneas, como en "\nhi". Sin embargo, si hay un último carácter en la primera línea, se devolverá en la variante Some. El operador ? en el medio nos da una forma concisa de expresar esta lógica, lo que nos permite implementar la función en una línea. Si no pudiéramos usar el operador ? en Option, tendríamos que implementar esta lógica usando más llamadas a métodos o una expresión match.

Tenga en cuenta que puede usar el operador ? en un Result en una función que devuelve Result, y puede usar el operador ? en un Option en una función que devuelve Option, pero no puede mezclarlos. El operador ? no convertirá automáticamente un Result en un Option o viceversa; en esos casos, puede usar métodos como el método ok en Result o el método ok_or en Option para hacer la conversión explícitamente.

Hasta ahora, todas las funciones main que hemos usado devuelven (). La función main es especial porque es el punto de entrada y salida de un programa ejecutable, y hay restricciones sobre cuál puede ser su tipo de retorno para que el programa se comporte como se espera.

Por suerte, main también puede devolver un Result<(), E>. La Lista 9-12 tiene el código de la Lista 9-10, pero hemos cambiado el tipo de retorno de main a ser Result<(), Box<dyn Error>> y agregado un valor de retorno Ok(()) al final. Este código ahora se compilará.

Nombre del archivo: src/main.rs

use std::error::Error;
use std::fs::File;

fn main() -> Result<(), Box<dyn Error>> {
    let greeting_file = File::open("hello.txt")?;

    Ok(())
}

Lista 9-12: Cambiar main para devolver Result<(), E> permite el uso del operador ? en valores de Result.

El tipo Box<dyn Error> es un objeto de trato, sobre el que hablaremos en "Usando Objetos de Trato que Permiten Valores de Diferentes Tipos". Por ahora, puede leer Box<dyn Error> como "cualquier tipo de error". Usar ? en un valor de Result en una función main con el tipo de error Box<dyn Error> está permitido porque permite que cualquier valor Err se devuelva temprano. Aunque el cuerpo de esta función main solo devolverá errores del tipo std::io::Error, al especificar Box<dyn Error>, esta firma seguirá siendo correcta incluso si se agrega más código que devuelva otros errores al cuerpo de main.

Cuando una función main devuelve un Result<(), E>, el programa ejecutable saldrá con un valor de 0 si main devuelve Ok(()) y saldrá con un valor distinto de cero si main devuelve un valor Err. Los programas ejecutables escritos en C devuelven enteros cuando salen: los programas que salen con éxito devuelven el entero 0, y los programas que tienen errores devuelven algún entero distinto de 0. Rust también devuelve enteros desde los programas ejecutables para ser compatible con esta convención.

La función main puede devolver cualquier tipo que implemente el trato std::process::Termination, que contiene una función report que devuelve un ExitCode. Consulte la documentación de la biblioteca estándar para obtener más información sobre la implementación del trato Termination para sus propios tipos.

Ahora que hemos discutido los detalles de llamar a panic! o devolver Result, volvamos al tema de cómo decidir cuál es el adecuado para usar en cada caso.

Resumen

¡Felicitaciones! Has completado el laboratorio de Errores Recuperables con Result. Puedes practicar más laboratorios en LabEx para mejorar tus habilidades.