Article table of contents
- foreword
- 8.1 Unrecoverable Error Panic
- 8.2 Recoverable Error Result
Foreword
This article introduces Rust error handling~
Rust does not have exceptions, but divides error handling into recoverable and unrecoverable errors.
Error Type | Description | Processing Technique |
---|---|---|
Recoverable errors | usually represent situations where reporting the error to the user and retrying the operation is reasonable, such as file not found | Result |
Unrecoverable error | usually is a synonym for bugs, such as trying to access past the end of the array | panic! |
8.1 Unrecoverable Error Panic
- Syntax:
panic!("Exception information");
- Set the environment variable
RUST_BACKTRACE=1
to print the exception stack. - Use the
panic::catch_unwind
function to let the developer catch Panic so that the program can continue without being aborted.- Try to avoid
panic::catch_unwind
as it may cause memory unsafety.
use std::panic; fn main() {<!-- --> let v = vec![1,2,3]; println!("{}", v[0]); let result = panic::catch_unwind(|| {<!-- --> prinln!("{}", v[99])}); assert!(result.is_err()); println!("{}", v[1]); } // 1 // panic error message // 2
- Try to avoid
8.2 Recoverable Error Result
8.2.1 Basic usage
Result
: Can be used to handle recoverable errors, it uses an enumeration to encapsulate the normal return value and error information.enum Result<T, E> {<!-- --> Ok(T), Err(E), }
T
andE
are generic type parameters.T
represents the type of data in theOk
member returned on success, andE
represents theErr
returned on failure Wrong type in member.
- The type
Result
will be automatically imported by Rust, no need to display the import scope, useOk(T)
orErr(E) directly code>.
- The
unwrap
andexpect
methods provided by theResult
type can implement similar functions of match pattern matching.unwrap()
: If no error is returned, continue execution, otherwise throw the error as panic.expect("custom error message")
: If no error is returned, continue execution, otherwise the custom error message will be thrown in the form of panic.
- Propagating errors: Rust provides the
?
operator to simplify code for use in functions that return a value of typeResult
, which is defined to do exactly the same job as match pattern matching Way.- If the value of Result is
Ok
, it will return the value inOk
and continue to execute the following code. - If the value of Result is
Err
, the value inErr
will be passed to the caller as the return value of the entire function. - The
?
operator is passed to thefrom
function, which is defined in the standard library'sFrom
trait to convert errors from a type for another type. The error type received by the?
operator is converted to the error type returned by the current function.
- If the value of Result is
- Combined with the match syntax, recoverable error handling can be performed after receiving a value of type
Result
:use std::fs::File; fn main() {<!-- --> let f = File::open("hello.txt"); let f = match f {<!-- --> Ok(file) => file, Err(error) => {<!-- --> // Recoverable error handling, return a value as needed or just panic! panic!("Problem opening the file: {:?}", error) }, }; }
8.2.2 matches different errors
- When calling a function in actual use, it is often possible to return more than one type of error, and developers will handle different errors differently.
- A structure type
io::Error
is provided in the standard library, and its members correspond to different error types that may be caused by io operations, which can usually be returned asErr
members. This structure has akind()
method that returns the value ofio::ErrorKind
, which will return an enumeration of different errors. Developers can use match expressions to implement different errors. perform different operations.use std::fs::File; use std::io::ErrorKind; fn main() {<!-- --> let f = File::open("hello.txt"); let f = match f {<!-- --> 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), }, }; }
- It can be seen that if you want to return multiple errors, you can customize an error enumeration and return it as
Err
. - There is also a more concise syntax, which will be introduced later.
- It can be seen that if you want to return multiple errors, you can customize an error enumeration and return it as
8.2.3 Convert recoverable errors to unrecoverable errors: unwrap and expect
unwrap
unwrap()
ofResult
:- If the value of Result is Ok, the value in Ok will be returned directly.
- If the value of Result is Err, call the
panic!
macro to throw the error message in Err.
- Disadvantage of unwrap: You cannot customize the error message when calling unwrap.
- unwrap example:
use std::fs::File; fn main() {<!-- --> let f = File::open("hello.txt").unwrap(); }
expect
- The difference between
expect
andunwrap
is thatexpect
can customize the error message. expect("error message")
ofResult
:- If the value of Result is Ok, the value in Ok will be returned directly.
- If the value of Result is Err, call the
panic!
macro to throw the error message provided by the developer when calling except.
- The advantage of except: can quickly find the location of the panic through custom information.
- except example:
use std::fs::File; fn main() {<!-- --> let f = File::open("hello.txt").expect("Failed to open hello.txt"); }
8.2.4 Propagation errors
Explanation of propagation errors
- Propagating errors is equivalent to the process of throwing exceptions upwards in java. The difference is that excluding exceptions in java does not distinguish between recoverable and unrecoverable errors.
- Due to the distinction between recoverable and unrecoverable errors in rust, unrecoverable errors are thrown using
panic!
, while recoverable errors are given by returningResult
. Therefore, the propagation error mentioned here mainly discusses the propagation of recoverable errors. If you want to propagate the error to the upper layer, it is usually repackaged into a newErr after matching the error in the match.
returns. - Example of propagating errors:
use std::io; use std::io::Read; use std::fs::File; fn read_username_from_file() -> Result<String, io::Error> {<!-- --> let f = File::open("hello.txt"); let mut f = match f {<!-- --> Ok(file) => file, Err(e) => return Err(e), // propagate error 1: pass the error of opening the file to the caller }; let mut s = String::new(); match f.read_to_string( &mut s) {<!-- --> Ok(_) => Ok(s), Err(e) => Err(e), // propagate error 2: pass the error of reading the file to the caller } } fn main() {<!-- --> let result = read_username_from_file(); }
- It can be seen that rust actually changes the
throw
operation in other languages to returnResult::Err
, thus realizing the unification of returning general values and propagating errors .
? operator
-
?
operator: A shortcut for propagating errors. -
?
operator usage:Result value?
, equivalent to// let var_name = Result value?; // Equivalent to let var_name = match Result value {<!-- --> Ok(value) => value, Err(e) => return Err(e), };
- It can be seen that in many cases, when developers get a Result value, they just want to get the value directly if there is no error. If there is an error, the error is passed to the upper-layer caller, and the upper-layer caller handles it. These are template code template code, so the
?
operator is there to simplify writing this one.
- It can be seen that in many cases, when developers get a Result value, they just want to get the value directly if there is no error. If there is an error, the error is passed to the upper-layer caller, and the upper-layer caller handles it. These are template code template code, so the
-
The role of the
?
operator:- If Result is Ok: The value in Ok is the result of the expression, and execution continues.
- If Result is Err: Err is the return value of the entire function, as if return was used.
-
The
?
operator can only be applied to functions of type Result. -
Examples of
?
expressions:use std::io; use std::io::Read; use std::fs::File; fn read_username_from_file() -> Result<String, io::Error> {<!-- --> // equivalent to also telling the caller that an error may occur let mut f = File::open("hello.txt")?; let mut s = String::new(); f.read_to_string( &mut s)?; Ok(s) }
-
The chained call form of the above example:
fn read_username_from_file() -> Result<String, io::Error> {<!-- --> let mut s = String::new(); File::open("hello.txt")?.read_to_string( &mut s)?; Ok(s) }
-
The main function
main()
can return what type can be the following two:()
: return emptyResult
: returns valid value or error
? and from function
- Trait
std::convert::From
has afrom
function: used to convert between errors. - Errors applied by
?
are implicitly handled by thefrom
function. The error type received byfrom
will be converted to the error type defined by the return type of the current function. - Purpose: For different error reasons, return the same error type.
- Precondition: Each error type implements a
from
function that converts to the returned error type.
- Precondition: Each error type implements a
8.3 When to panic! When to use Result
8.3.1 General principles
- When defining a function that may fail, return
Result
in preference, otherwisepanic!
. - Use
panic!
instead ofResult
to indicate that the error cannot be recovered and the program must be terminated; useResult
instead ofpanic!
, it means that it is possible for the program to be restored and whether or not to restore the decision is up to the caller. - Don't be afraid to throw
panic!
, usually the function will abide by the contract. If the caller does not abide by the contract and causes an unrecoverable error, it is the caller's responsibility, and the program should terminate and let the caller fix the code.
8.3.2 Write examples, prototype code, tests
Scenarios where panic!
can be used:
- Demonstrate some concepts: eg
unwrap
. - When writing prototype code: first write the theme structure of the code, and then try the
unwrap
orexpect
tags where the details need to be implemented, and add it when the code is improved later. . - When writing test code: You can use
unwrap
andexpect
to mark when the test encounters non-compliant returns.
8.3.3 When the developer has more information than the compiler
- In some scenarios, the developer can determine that
Result
returnsOk
, but when the compiler checks, the developer needs to considerErr due to the needs of the mechanism code> situation: At this time, you can directly use
unwrap
to throwErr
.
8.3.4 Guidelines for Error Handling
panic!
is best used when the code may end up in a broken state. Bad State: Some assumptions, guarantees, conventions or immutability of the program behavior are broken when the developer writes the logic. For example, an illegal value, a contradictory value, or a missing value is passed into the code, and one of the following:
- This corrupted state is not something expected to happen.
- After this, the developer's code cannot run in this corrupted state.
- There is currently no better way for developers to deal with the current situation.