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:
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