Rust error handling

overview

Most languages ​​do not distinguish between recoverable and unrecoverable errors, and treat them uniformly like exceptions.
Rust has no exceptions. Instead, it has the Result<T, E> type, which handles recoverable errors, and the panic! macro, which halts program execution when it encounters an unrecoverable error.

panic!

Handle unrecoverable errors

  • When this macro is executed,
    the program will print an error message
    Unwind and clear the stack (Stack)
    and exit the program.

When a panic occurs, the program will start unwinding by default, which means that Rust will backtrack the stack and clean up the data of every function it encounters, but this backtracking and cleaning process is a lot of work. Another option is to abort, which exits the program without cleaning up the data. Then the memory used by the program needs to be cleaned up by the operating system.

panic! It may be a direct macro call, and another may be a panic in another library caused by a bug in our code!

//1
fn main() {
    
    
    panic!("crash and burn");
}
//2
fn main() {
    
    
    let v = vec![1, 2, 3];

    v[99];
}

Result

Handling recoverable errors
This part is more interesting

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

There are two variants, both of which are associated with data, T represents the type of data in the Ok member returned on success, and E represents the type of error in the Err member returned on failure.

  • Use match expressions
use std::fs::File;
fn main(){
    
    
	let f = File::open("hello.txt");
	  let f = match f {
    
    
        Ok(file) => file,
        Err(error) => panic!("Problem opening the file: {:?}", error),
    };
}

When the result is Ok, return the file value in the Ok member, and then assign the file handle to the variable f. After match, we can use this file handle to read and write.

Another branch of match handles the case of getting an Err value from File::open. In this case, we choose to call the panic! macro.

  • It's a bit more complicated.
    We want to behave differently for different causes of File::open errors.
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)
            }
        },
    };
}

The type of error is io::Error, which is a structure in the annotation library. There is a kind method in it, which gets the Errorkind type value, which is also an enumeration.
The condition checked in match is whether the return value of error.kind() is the NotFound member of ErrorKind. If yes, try to create the file via File::create. However, since File::create may also fail, an inner match statement needs to be added. When the file cannot be opened, a different error message is printed. The last branch of the outer match is left unchanged, so that the program will panic on any error other than that the file does not exist.

We can find that match is really powerful, but it is also very basic. Multi-layer nested match will make the structure less clear. For this reason, we can use closure (closure)

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

fn main() {
    
    
    let f = 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);
        }
    });
}
  • The shortcut method of unwrap
    match
    Function: If the Result value is a member Ok, unwrap will return the value in Ok. If Result is a member Err, unwrap will call panic! for us.
use std::fs::File;


fn main() {
    
    
//	let f = File::open("hello.txt");
//	  let f = match f {
    
    
//        Ok(file) => file,
//        Err(error) => panic!("Problem opening the file: {:?}", //error),}

    let f = File::open("hello.txt").unwrap();
}

  • Compared with unwrap, expect can specify error information
use std::fs::File;

fn main() {
    
    
    let f = File::open("hello.txt").expect("Failed to open hello.txt");
}


Result propagates errors

return the error to the caller

When writing a function that actually calls some operation that may fail, in addition to handling the error in this function, you can also choose to let the caller know about the error and decide how to deal with it.

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

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),
    };

    let mut s = String::new();

    match f.read_to_string(&mut s) {
    
    
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}

Pay attention to the return value of the function: Result<String, io::Error>. Here, io::Error is chosen as the return value of the function because it happens to be the error return value of the two operations that may fail in the function body: File: :open function and read_to_string method.

  • ? operator
    Simplifies the way errors are propagated
    to achieve the same operation as just
use std::fs::File;
use std::io;
use std::io::Read;

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

If the value of Result is Ok, the expression will return the value in Ok and program execution will continue. If the value is Err, the value in Err will be used as the return value of the entire function, as if using the return keyword, so that the error value is propagated to the caller.

quilt? Application errors will be implicitly handled by the from function. This from is used for conversion between different error types.
When? When the from function is called, the error type it receives will be converted to the error type defined by the return type of the current function.

  • ?Chain calls
use std::fs::File;
use std::io;
use std::io::Read;

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)
}
  • ? with the main function
use std::error::Error;
use std::fs::File;

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

    Ok(())
}

The Box type is a trait object (trait object), and Box can be understood as "any type of error".
When the main function returns Result<(), E>, if main returns Ok(()), the executable program will exit with a value of 0, and if main returns an Err value, it will exit with a non-zero value;


Reference: "Rust Programming Language"

Guess you like

Origin blog.csdn.net/weixin_61631200/article/details/127833340