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 panic
macros
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=1
can 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