Trust learning — error handling

Time:2021-2-27

Rust divides errors into two categories: recoverable and unrecoverable. For recoverable errors, such as the file not found error, it is reasonable to report the problem to the user and then try the operation again. An unrecoverable error is always a symptom of an error, such as an attempt to access a location other than the end of an array.

Unrecoverable errors andpanic!

Sometimes bad things happen in our code, and there’s nothing you can do about it. In these cases, rust executes the panic! Macro command. When the panic! Macro is executed, the program prints a failure message, expands and cleans the stack, and then exits. The most common situation is when an error is detected and the programmer doesn’t know how to handle it.

Let’s try it in a simple programpanic!

fn main() {
    Panic! ("crash out");
}

D:\learn\cargo_learn>cargo run
   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)
    Finished dev [unoptimized + debuginfo] target(s) in 0.53s
     Running `target\debug\cargo_learn.exe`
Thread 'main' panicked at '\ main.rs :2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\debug\cargo_learn.exe` (exit code: 101)

panic!Macro command and Java in JSthrow ErrorThey belong to the same property, so they are the same concept relative to nodejs

usepanic!to flash back
Let’s use an example to see whenpanin!The macro command will be called. The call is not our code, but the call from the library. When there is an error in our code.

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

    v[99];
}

D:\learn\cargo_learn>cargo run
   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)
    Finished dev [unoptimized + debuginfo] target(s) in 0.55s
     Running `target\debug\cargo_learn.exe`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src\main.rs:4:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\debug\cargo_learn.exe` (exit code: 101)

Here, we try to access the 100th element of the vector (the index starts at zero, so it’s at index 99), but it has only three elements. In this case, rust willpanic. An element should be returned with [] but rust does not return the correct element here if an invalid index is passed.

Recoverable errors and errorsResult

Most errors are not serious enough to require the program to stop completely. Sometimes, when a function fails, it’s for reasons that can be easily explained and responded to. For example, if you try to open a file and the operation fails because the file does not exist, you may want to create the file instead of terminating the process. For example:

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

T and E are generic type parameters: T represents the type of value to be returned in OK variable in case of success, e represents the type of error to be returned in case of failure in err variable. Because result has these common type parameters, we can use the result type and the functions defined by the standard library in many different cases (the success value and error value we want to return may be different).

Let’s call a function that returns the result value, because it may fail. Try opening a file:

use std::fs::File;

fn main() {
    let f = File::open("hello.txt");
    println!("{:?}", f)
}

D:\learn\cargo_learn>cargo run
   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)
    Finished dev [unoptimized + debuginfo] target(s) in 0.56s
     Running `target\debug\cargo_learn.exe`
Err (OS {Code: 2, kind: notfound, message: "the system cannot find the specified file. " })

In particular, f in the above example isResult<File,Error>If other types are specified, an error will be reported, such as:

use std::fs::File;

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

D:\learn\cargo_learn>cargo run
   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)
error[E0308]: mismatched types
 --> src\main.rs:4:18
  |
4 |     let f: u32 = File::open("hello.txt");
  |            ---   ^^^^^^^^^^^^^^^^^^^^^^^ expected `u32`, found enum `std::result::Result`
  |            |
  |            expected due to this
  |
  = note: expected type `u32`
             found enum `std::result::Result<std::fs::File, std::io::Error>`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.
error: could not compile `cargo_learn`.

To learn more, run the command again with --verbose.

We can also use the match expression to customize different operations on the return value

use std::fs::File;

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

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

D:\learn\cargo_learn>cargo run
   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)
    Finished dev [unoptimized + debuginfo] target(s) in 0.54s
     Running `target\debug\cargo_learn.exe`
Thread 'main' panicked at 'failed to open file: OS {Code: 2, kind: not found, message: "the system cannot find the specified file. " }', src\ main.rs :8:23
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\debug\cargo_learn.exe` (exit code: 101)

Match different errors
The code in the above example willpanic!No matter why file:: open failed. Instead, we need to take different actions for different failure reasons: if file:: open fails because the file does not exist, we want to create the file and return the handle to the new file. If file:: open fails for any other reason (for example, because we don’t have permission to open the file), we still want the code to crash! As in the above example, add an internal match expression:

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! ("failed to create file: {:?}", e), ",
            },
            other_error => {
                "Open file failed: {:?}", other_ error)
            }
        },
    };
}

errorpanicShortcut for:unwrapandexpect

Using match works well, but it can be a bit lengthy and doesn’t always convey the intent well. The result < T, E > type has many auxiliary methods defined on it to perform various tasks. One of them is called unwrap, which is a quick way. If the result value is an OK variable, the expansion returns the value inside the OK. If the result value is an err variant, unwrap causespanic!Our macro. This isunwrapExample of operation:

use std::fs::File;

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

D:\learn\cargo_learn>cargo run
   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)
warning: unused variable: `f`
 --> src\main.rs:4:9
  |
4 |     let f = File::open("hello.txt").unwrap();
  |         ^ help: if this is intentional, prefix it with an underscore: `_f`
  |
  = note: `#[warn(unused_variables)]` on by default

warning: 1 warning emitted

    Finished dev [unoptimized + debuginfo] target(s) in 0.55s
     Running `target\debug\cargo_learn.exe`
Thread 'main' panicked at 'called' result:: unwrap()'on an 'err' Value: OS {Code: 2, kind: not found, message: "the system can't find the specified file. " }', src\ main.rs :4:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\debug\cargo_learn.exe` (exit code: 101)

Another method:expect, andunwrapSimilar, will cause the callpanic!. useexpectAndunwrapIt can be a good way to customize the error message. useexpectThe method is as follows

use std::fs::File;

fn main() {
    let f = File::open(" hello.txt "). Expect (" to open the file hello.txt Wrong! ");
}

D:\learn\cargo_learn>cargo run
   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)
warning: unused variable: `f`
 --> src\main.rs:4:9
  |
4 |     let f = File::open(" hello.txt "). Expect (" to open the file hello.txt Wrong! ");
  |         ^ help: if this is intentional, prefix it with an underscore: `_f`
  |
  = note: `#[warn(unused_variables)]` on by default

warning: 1 warning emitted

    Finished dev [unoptimized + debuginfo] target(s) in 0.56s
     Running `target\debug\cargo_learn.exe`
Thread 'main' panicked at 'open file hello.txt Wrong! : OS {Code: 2, kind: notfound, message: "the system cannot find the specified file. " }', src\ main.rs :4:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\debug\cargo_learn.exe` (exit code: 101)

Error delivery

When writing a function whose implementation calls may fail, you can return the error to the calling code so that it can decide what to do instead of dealing with the error in the function. This is called error passing and gives more control to the calling code, where there may be more information or logic to dictate how the error should be handled than is available in the context of the code.
For example: the following function reads specific data from a file. If the file does not exist or cannot be read, this function returns these errors to the code calling this function:

#![allow(unused_variables)]
fn main() {
    use std::fs::File;
    use std::io;
    use std::io::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),
        }
    }

    let test = read_username_from_file();

    println!("{:?}", test);
}

D:\learn\cargo_learn>cargo run
   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)
    Finished dev [unoptimized + debuginfo] target(s) in 0.66s
     Running `target\debug\cargo_learn.exe`
Err (OS {Code: 2, kind: notfound, message: "the system cannot find the specified file. " })

This function can be written in a shorter way, but we’ll do a lot of manual work first to explore error handling; finally, we’ll show you a shorter way. Let’s take a look at the return type of the function: result < string, IO:: error >. This means that the function will return the value of the type result < T, E >, where the generic parameter t is filled with the specific type string and the generic type E is filled with the specific type IO:: error. If the function executes successfully without any problems, the code calling the function will receive an OK value containing the string of the user name that the function reads from the file. If the function encounters any problems, the code calling the function will receive an err value containing an IO:: error instance that contains more information about the problem. We choose io:: Error as the return type of this function, because it happens to be the type of error value returned from the two operations (which may fail) that we call in the main body of the function: File:: open function and read_. to_ String method.

The body of the function starts by calling the file:: open function. Then we process the returned result value, which is similar to the match in listing 9-4, not just the callpanic!In the case of err, we will return from this function as soon as possible, and pass the callback code with the error value of file:: open as the error value of this function. If file:: open succeeds, we store the file handle in the variable F and continue.

Then, create a new string in the variable s and call read on the file handle in F_ to_ String method to read the contents of the file into S. read_ to_ The string method also returns results, because even if file:: open succeeds, it may fail. So we need another match to handle the result: if read_ to_ String success indicates that our function has succeeded, and we have returned the user name from the file, which has been wrapped by OK. If read_ to_ String failed, we will return the error value in the same way as the error value in the matching of the return value of file:: open. However, we don’t need to explicitly say return, because this is the last expression in the function.

Then, the code calling this code will process the OK value containing the user name or the err value containing IO:: error. We don’t know how the calling code will handle these values. If you call the code to get the err value, there may be a problempanic!And crash the program, use the default user name, or look up the user name from a location other than the file. We don’t have enough information to understand what the calling code actually does, so we propagate all the success or error messages up so that they can be handled correctly.

Shortcut to error delivery:?operation

use?The operation realizes the same function as the above example

#![allow(unused_variables)]
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 test = read_username_from_file();

    println!("{:?}", test);
}

D:\learn\cargo_learn>cargo run
   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)
    Finished dev [unoptimized + debuginfo] target(s) in 0.69s
     Running `target\debug\cargo_learn.exe`
Err (OS {Code: 2, kind: notfound, message: "the system cannot find the specified file. " })

The? Operator, the code placed after the result value is defined, works almost the same way as the matching expression we defined to handle the result value in the above example. If the value of result is OK, the value in OK is returned from the expression and the program continues. If the value is err, err will be returned from the entire function as we used the return keyword to propagate the error value to the calling code.

In this way, the above example can be more simply expressed as:

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

We have moved the creation of a new string in s to the beginning of the function; that part has not changed. Instead of creating the variable F, we will use the_ to_ The call to string links directly to theFile::open("hello.txt")?On the results. We have another one?In read_ to_ The end of the string call, whenFile::openAnd read_ to_ When all strings succeed without an error, we still return an OK value containing the user name in S. Again, the function is the same as the above example; it’s just a different, more ergonomic way of writing.

There is a simpler way to achieve this

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

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

Reading a file into a string is a fairly common operation, so rust provides a convenient wayfs::read_to_stringFunction, which can open a file, create a new string, read the contents of the file, put the contents into the string and return. Of course, usingfs::read_to_stringIt doesn’t give us a chance to explain all the error handling, so we started with a longer explanation.

?Operators can be used in functions that return results

?The operator can be used in a function with a return type of result because its definition works in the same way as the match expression defined in the previous example. The part that requires the return type to be result is return err (E), so the return type of the function can be result to be compatible with this return.
Let’s see if we use?What will happen? You will remember that the return type of the operator in the main function is ():

use std::fs::File;

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

D:\learn\cargo_learn>cargo run
   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)
error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `std::ops::Try`)
 --> src\main.rs:4:13
  |
3 | / fn main() {
4 | |     let f = 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 `std::ops::Try` is not implemented for `()`
  = note: required by `std::ops::Try::from_error`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
error: could not compile `cargo_learn`.

To learn more, run the command again with --verbose.

This error indicates that we are only allowed to use?Returns result or option or implementationstd::ops::TryOperators in other types of functions. When you write code in a function that does not return one of these types, use? When you call anotherResult<T, E>There are two options to solve this problem. One technique is to change the return type of a function toResult<T, E>. Another technique is to usematchorResult<T, E>One way is to deal with it in an appropriate wayResult<T, E>

The main function is special and its return type must be restricted. One valid return type of main is (), and more conveniently, the other isResult<T, E>, as follows:

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

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

    Ok(())
}

D:\learn\cargo_learn>cargo run
   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)
warning: unused variable: `f`
 --> src\main.rs:5:9
  |
5 |     let f = File::open("hello.txt")?;
  |         ^ help: if this is intentional, prefix it with an underscore: `_f`
  |
  = note: `#[warn(unused_variables)]` on by default

warning: 1 warning emitted

    Finished dev [unoptimized + debuginfo] target(s) in 0.72s
     Running `target\debug\cargo_learn.exe`
Error: OS {Code: 2, kind: notfound, message: "the system cannot find the specified file. " }
error: process didn't exit successfully: `target\debug\cargo_learn.exe` (exit code: 1)

usepanic!Or not

Rust’s error handling capabilities are designed to help write more reliable code.panic!A macro indicates that the program is in an unhandled state, allowing us to tell the process to stop instead of trying to process with invalid or incorrect values.ResultEnumeration uses rust’s type system to indicate that the operation may fail in a way that the code can recover. We can use itResultThe code that calls our code also needs to deal with potential successes or failures.panic!andResultIn appropriate cases, it will make our code more reliable when encountering unavoidable problems.

Recommended Today

Third party calls wechat payment interface

Step one: preparation 1. Wechat payment interface can only be called if the developer qualification has been authenticated on wechat open platform, so the first thing is to authenticate. It’s very simple, but wechat will charge 300 yuan for audit 2. Set payment directory Login wechat payment merchant platform( pay.weixin.qq . com) — > Product […]