Error handling


Release date:2023-11-09 Update date:2023-11-10 Editor:admin View counts:325

Label:

Error handling

Rust has a unique mechanism for handling exception cases, unlike those in other languages try the mechanism is so simple.

First of all, there are generally two kinds of errors in the program: recoverable errors and unrecoverable errors.

A typical case of recoverable error is a file access error. If access to a file fails, it may be because it is being occupied and is normal, which we can solve by waiting.

But there is also an error that is caused by logic errors that cannot be resolved in programming, such as accessing locations other than the end of the array.

Most programming languages do not distinguish between these two errors and use the Exception (exception) class to represent errors. Not in Rust Exception .

For recoverable errors Result<T, E> class, and for unrecoverable errors``panic!`` macros to deal with.

Unrecoverable error

This chapter has not been devoted to the syntax of Rust macros before, but has already been used println! Macros, because these macros are relatively simple to use, so we don’t need to master them thoroughly for thetime being. We can use the same method and first learn to use pan how to use macros.

Example

fn main() {
    panic!("error occured");
    println!("Hello, Rust");
}

Running result:

thread 'main' panicked at 'error occured', src\main.rs:3:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

Obviously, the program doesn’t run to println! (“Hello, Rust”) as expected, but on panic! The macro stopped running when it was called.

An unrecoverable error must cause the program to be fatally hit and stop running.

Let’s look at the two lines of error output:

  • The first line outputs panic! the location of the macro call and the error message it outputs.

  • The second line is a prompt, which translates into Chinese as “run through the`RUST_BACKTRACE=1‘environment variable to display backtracking”. Next we will introduce backtrace.

Following the example just now, let’s create a new terminal in VSCode:

Image0

Set environment variables in the newly created terminal (different terminal methods are different, here are two main methods):

If you are in a Windows system version of Windows 7 or above, the terminal command line used by default is Powershell , use the following command:

$env:RUST_BACKTRACE=1 ; cargo run

If you are using a UNIX system such as Linux or macOS, the default is bash command line, use the following command:

RUST_BACKTRACE=1 cargo run

Then you will see the following text:

thread 'main' panicked at 'error occured', src\main.rs:3:5
stack backtrace:
  ...
  11: greeting::main
             at .\src\main.rs:3
  ...

Backtracking is another way to handle unrecoverable errors. It expands the running stack and outputs all the information, and then the program still exits. The ellipsis above omits a lot of output information, and we can findthe panic! error triggered by macro.

Recoverable error

This concept is very similar to exceptions in the Java programming language.In fact, in C language, we often set the return value of the function to aninteger to express the error encountered by the function. In Rust, we use the Result<T, E> , the enumeration class is used as a return value to express the exception:

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

The return values of functions that may produce exceptions in the Rust standard library are of type Result. For example, when we try to open a file:

Example

use std::fs::File;
fn main() {
    let f = File::open("hello.txt");
    match f {
        Ok(file) => {
            println!("File opened successfully.");
        },
        Err(err) => {
            println!("Failed to open the file.");
        }
    }
}

If hello.txt file does not exist and will be printed "Failed toopen the file." .

Of course, we talked about it in the enumeration section. if let syntaxcan be simplified match grammatical block:

Example

use std::fs::File;
fn main() {
    let f = File::open("hello.txt");
    if let Ok(file) = f {
        println!("File opened successfully.");
    } else {
        println!("Failed to open the file.");
    }
}

If you want a recoverable error to be handled as an unrecoverable error, the Result class provides two methods: ``unwrap () `` and expect (message: & str):

Example

use std::fs::File;
fn main() {
    let f1 = File::open("hello.txt").unwrap();
    let f2 = File::open("hello.txt").expect("Failed to open.");
}

This program is equivalent to Result for Err called when the panic! Macro. The difference between the two is expect , you can talk to panic! The macro sends a specified error message.

Recoverable error delivery

We were talking about how to handle receiving errors, but what if we write afunction ourselves and want to pass it on when we encounter an error?

Example

fn f(i: i32) -> Result<i32, bool> {
    if i >= 0 { Ok(i) }
    else { Err(false) }
}
fn main() {
    let r = f(10000);
    if let Ok(v) = r {
        println!("Ok: f(-1) = {}", v);
    } else {
        println!("Err");
    }
}

Running result:

Ok: f(-1) = 10000

The function f in this program is the source of the error, so now let’s write another function g that passes the error:

Example

fn g(i: i32) -> Result<i32, bool> {
    let t = f(i);
    return match t {
        Ok(i) => Ok(i),
        Err(b) => Err(b)
    };
}

The function g passes a possible error in the function f (g here is just a simple example, but in fact the function that passes the error usually contains a lot of other operations).

It’s a bit lengthy to write like this, and you can use it in Rust Result add after object ? operator sets the same kind of Err pass it directly out:

Example

fn f(i: i32) -> Result<i32, bool> {
    if i >= 0 { Ok(i) }
    else { Err(false) }
}
fn g(i: i32) -> Result<i32, bool> {
    let t = f(i)?;
    Ok(t) // Because it is certain that t is not Err, t is already of type i32 here
}
fn main() {
    let r = g(10000);
    if let Ok(v) = r {
        println!("Ok: g(10000) = {}", v);
    } else {
        println!("Err");
    }
}

Running result:

Ok: g(10000) = 10000

The actual function of the symbol ? is to set the Result .The non-abnormal value of the class is taken out directly, and if there is an exception, the exception will be taken out. Result go back out. Therefore, the ? symbol is only used to return a value of type Result<T, E> where the function of E type must be the same as ? what is handled Result of E type is the same.

kind Method

So far, Rust doesn’t seem to be like try block can also make the same kind of exception anywhere directly get the same syntax, but this does not mean that Rust can not be implemented: we can completely put try Blocksare implemented in separate functions, passing all exceptions out for resolution. In fact, this is the programming method that a well-differentiated program should follow: attention should be paid to the integrity of independent functions.

But this needs to judge Result’s Err type, getting. The function of Err type is kind() .

Example

use std::io;
use std::io::Read;
use std::fs::File;
fn read_text_from_file(path: &str) -> Result<String, io::Error> {
    let mut f = File::open(path)?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}
fn main() {
    let str_file = read_text_from_file("hello.txt");
    match str_file {
        Ok(s) => println!("{}", s),
        Err(e) => {
            match e.kind() {
                io::ErrorKind::NotFound => {
                    println!("No such file");
                },
                \_ => {
                    println!("Cannot read the file");
                }
            }
        }
    }
}

Running result:

No such file

Powered by TorCMS (https://github.com/bukun/TorCMS).