[Learn Rust Programming with Xiaojia] 9. Error Handling

Series Article Directory

[Learn Rust programming with Xiaojia] 1. Basics of Rust programming
[Learn Rust programming with Xiaojia] 2. Use of Rust package management tools
[Learn Rust programming with Xiaojia] 3. Basic programming concepts of Rust
[Learn Rust programming with Xiaojia] 】4. Understand the concept of ownership in Rust
【Learn Rust programming from Xiaojia】5. Use structures to associate structured data
【Learn Rust programming from Xiaojia】6. Enumeration and pattern matching
【Learn Rust programming from Xiaojia】7. Use packages (Packages), unit packages (Crates) and modules (Module) to manage projects
[Learn Rust programming with Xiaojia] 8. Common collections
[Learn Rust programming with Xiaojia] 9. Error handling (Error Handling)

foreword

The reliability of Rust comes from error handling. In most cases, errors are prompted and processed at compile time. Rust divides errors into two broad categories: unrecoverable and recoverable errors. For recoverable errors, such as a file not found, it is tempting to just report the problem to the user and retry the operation. Unrecoverable errors are always symptoms of bugs, like trying to access past the end of an array, so we want to stop the program immediately.

Most languages ​​do not distinguish between the two types of errors and handle both in the same way, using mechanisms such as exceptions. There are no exceptions in Rust.

The main teaching material refers to "The Rust Programming Language"


1. Unrecoverable error (panic!)

1.1. Unrecoverable errors

For the case of panic

  • By default, when a panic occurs, the program unwinds the call stack (a lot of work), and Rust walks back down the call stack, cleaning up the data in each encountered function
  • Immediately terminate the call stack, do not clean up, stop the program directly, and the memory needs to be cleaned up by the OS

If you want your binary to be smaller, you can change the setting from unwinding to abort (Cargo.toml)

[profile.release]
panic = 'abort'

1.2. Using panicmacros

panic!A panic can be thrown using a macro

fn main() {
    
    
    panic!("crash and burn!");
}

operation result

 cargo run
   Compiling my-project v0.1.0 (~/Desktop/code/rust_code/my-project)
    Finished dev [unoptimized + debuginfo] target(s) in 0.86s
     Running `target/debug/my-project`
thread 'main' panicked at 'crash and burn!', src/main.rs:5:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

1.3, index out of bounds error

Example of index out of bounds:

fn main() {
    
    
    let v = vec![1,2,3];
    v[99];
}

operation result:

   Compiling my-project v0.1.0 (~/code/rust_code/my-project)
    Finished dev [unoptimized + debuginfo] target(s) in 0.52s
     Running `target/debug/my-project`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:6:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

From the error message, it can be seen that setting the environment variable RUST_BACKTRACE=1can display the traceback information; setting it to RUST_BACKTRACE=full
can see more detailed information.

2. Recoverable errors (Result<T,E>)

Most errors are not serious enough to stop execution beyond recovery, so you can recover from errors by capturing the Result enumeration type.

2.1, Result enumeration type definition

enum Result<T, E> {
    
      // T, E 都是泛型参数
    Ok(T),
    Err(E),
}
  • T: When the operation is successful, the type of data returned in the OK variant;
  • E: When the operation fails, the type of data returned in the Err variant;

2.2. Use of Result enumeration

2.2.1. A way to deal with Result: match expression

use std::fs::File;

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

    let _f = match f {
    
    
        Ok(file) => file,
        Err(error) =>{
    
    
            panic!("Error open file {:?}",error)
        }
    };
}

2.2.2. Matching different 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!("文件创建失败:{:?}", e)
            }
            other_error => panic!("打开文件失败:{:?}", other_error),
        }
    };
}

2.2.3. Use unwrap_or_else to optimize the above code

Using closures can make the code more concise and save a lot of match nesting

use core::panic;
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!("文件创建失败:{:?}", error);
            })
        }else{
    
    
            panic!("打开文件失败:{:?}", error);
        }
    });
}

2.2.4, using the unwrap method

A shortcut for match expressions

  • If the Result result is OK, return the value in OK;
  • If the Result result is Err, call the panic! macro;
   let f = File::open("hello.txt").unwrap();

2.2.5. Use expect method

Expect is similar to unwrap, unwrap cannot specify an error message, but expect can specify an error message.

let f = File::open("hello.txt").expect("打开文件失败");

2.2.6. Propagating errors

Some people like to handle it in place, and some people need to pass the error to the upper caller for processing. rust provides? Let's spread the filth.

#![allow(unused)]
fn main() {
    
    
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)
}

let res = read_username_from_file();
dbg!(&res);
}

Not all errors are implicitly Trait std::convert::From converted by the above from function. It is used to return the same error type for different error reasons: as long as each error type implements the from function converted to the returned error type.

?The question mark operator can only be used for return values ​​that are Result or Option, or types that implement Try.

2.2.7. Main function return value

The default return value of the main function is ()of type, we can also let the main function return the type of Result.

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

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

3. When to use panic!

3.1. General principles

When defining a function that may fail, return Result first, otherwise return panic.

3.2. Use panic! scene

3.2.1. Demonstrate some concepts: unwrap

3.2.2. Prototype code: unwrap, expect

3.2.3. Test: unwrap, expect

3.2.4. Deterministic results.

Sometimes you have more information than the compiler, and you can be sure that the Result is OK: unwrap

use std::net::IpAddr;

fn main() {
    
    
    let home: IpAddr= "127.0.0.1".parse().unwrap();

}

3.3. Guiding suggestions for error handling

When the code may end up in a bad state (Bad State), it is best to panic! .

Bad State: Some assumption, guarantee, contract, or immutability is broken.

  • Example: Illegal, contradictory, or missing values ​​are passed into the code
  • and one of the following
    • This corrupted state is not something that is expected to happen occasionally
    • Until then, your code cannot run if it is in this broken state;
    • There isn't a good way to encode this information (in a corrupted state) in the type you're using;

Summarize

That's all for today

  • Explained the panic macro and the use of panic
  • Explained the use of the Result enumeration type
  • Explain when to use panic

Guess you like

Origin blog.csdn.net/fj_Author/article/details/131428261