In programming languages, errors are handled in two general ways:

  • An exception is thrown
  • Return as a value (return an error code or custom error type)

Rust returns errors as values and provides native error handling. In Rust, errors fall into two categories:

  • Recoverable error(recoverable) –Result<T, E>
  • Unrecoverable error(unrecoverable) –panic!

panic!

panic! Is a macro provided by Rust that, when executed, prints an error message, expands and cleans up stack data.

panic! Causes the current thread to terminate, or even the entire program to terminate. Therefore, panic! This is often used in written examples or test code.

Example: Call Panic! The macro

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

When there is a panic! By default, the program unwinding; that is, it goes back through the stack and cleans up the data for every function it encounters. Alternatively, abort can be used to exit the program without cleaning up the data, which requires the operating system to clean up the memory. To terminate directly, add the following configuration to the Cargo. Toml file:

# Under this configuration, the binaries generated by the project will be smaller.
[profile.release]
panic = 'abort'
Copy the code

Stack trace information

When the program encounters Panic! Upon exit, it prints an error message (namely, Panic! () in parentheses), and where the error occurred. And it will prompt you how to view the stack backtrace information.

Example: Make an unrecoverable error

fn main() {
    let v = vec![1.2.3];

    v[99];
}
Copy the code

Run the code above:

PS D:\vscode\rust_demo\hello> cargo run Compiling Hello v0.1.0 (D:\vscode\rust_demo\hello) Finished dev [unoptimized + Debuginfo] target(s) in 1.18s Running 'target' debug 'hello.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\hello.exe` (exit code: 101) PS D:\vscode\rust_demo\hello>Copy the code

The output prompts us to set the RUST_BACKTRACE environment variable to display the stack traceback information. Re-run the program as prompted, and we should see the detailed stack traceback:

PS D:\vscode\rust_demo\hello> $Env:RUST_BACKTRACE=1 PS D:\vscode\rust_demo\hello> PS D:\vscode\rust_demo\hello> cargo Exe 'thread 'main' panicked at run Finished dev [unoptimized + debuginfo] target(s) in 0.02s Running' target\debug\hello  'index out of bounds: the len is 3 but the index is 99', src\main.rs:4:5 stack backtrace: 0: std::panicking::begin_panic_handler at /rustc/18bf6b4f01a6feaf7259ba7cdae58031af1b7b39\/library\std\src\panicking.rs:475  1: core::panicking::panic_fmt at /rustc/18bf6b4f01a6feaf7259ba7cdae58031af1b7b39\/library\core\src\panicking.rs:85 2: core::panicking::panic_bounds_check at /rustc/18bf6b4f01a6feaf7259ba7cdae58031af1b7b39\/library\core\src\panicking.rs:62 3: core::slice::{{impl}}::index<i32> at C:\Users\wangzk\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\slice\mod.rs:3282 4: core::slice::{{impl}}::index<i32,usize> at C:\Users\wangzk\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\slice\mod.rs:3123 5: alloc::vec::{{impl}}::index<i32,usize> at C:\Users\wangzk\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\alloc\src\vec.rs:2000 6: hello::main at .\src\main.rs:4 7: core::ops::function::FnOnce::call_once<fn(),tuple<>> at C:\Users\wangzk\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\ops\function.rs:2 27 note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. error: process didn't exit successfully: `target\debug\hello.exe` (exit code: 101) PS D:\vscode\rust_demo\hello>Copy the code

The above example runs on a Windows system and sets environment variables differently from Linux. If CMD is used, run set RUST_BACKTRACE=1. If PowerShell is used, run $Env:RUST_BACKTRACE=1.

Option

Option

is an enumerated type provided by the Rust standard library that has only two members:

  • Some(T) — contains a value of type T
  • None — Indicates no value

When Option

is used as a parameter to a function or as a return value type, the function creator or caller must deal with the enumeration value being None to reduce potential bugs.

Example: Look for the needle character in the string haystack and return the index for that character

// The return value is' Option
      
        ', with 'None' to indicate that the needle character was not found
      
fn find(haystack: &str, needle: char) - >Option<usize> {
    for (offset, c) in haystack.char_indices() {
        if c == needle {
            return Some(offset); }}None
}
Copy the code

unwrap

The unwrap function for Option

returns a value of type T in Some(T). If the enumeration value is None, panic occurs. Therefore, this function is generally not recommended. The None value should be handled using pattern matching, or using the following three functions:

  • unwrap_or– returnSome(T)Contains or returns the specified default value.
  • unwrap_or_else– returnSome(T)Contains values that are either evaluated by a closure.
  • unwrap_or_default– returnSome(T)Contains a value or a default value of type T.

Example: Find the extension in the file name

fn extension_explicit(file_name: &str) - >OptionThe < &str> {
    // Pattern matching
    match find(file_name, '. ') {
        None= >None.Some(i) => Some(&file_name[i+1. ] ),}}fn main() {
    // Pattern matching
    match extension_explicit("foo.rs") {
        None= >println!("no extension"),
        Some(ext) => assert_eq!(ext, "rs"),}// If 'None' occurs, panic occurs
    let extension = extension_explicit("foo.rs").unwrap();
    println!("extension: {}", extension);

    // If 'None' is encountered, the default value specified is returned: 'rs'
    let extension = extension_explicit("foo").unwrap_or("rs");
    println!("extension: {}", extension);

    // If None is encountered, the default value of & STR is returned
    let extension = extension_explicit("foo").unwrap_or_default();
    println!("extension: {}", extension);
}
Copy the code

map

The map function for Option

is used to map one enumeration type Option

to another enumeration type Option
.

The map argument is a closure that specifies how to map.

Example: Find the extension in the file name

// Use the 'map' function instead of pattern matching, which is much cleaner
fn extension(file_name: &str) - >OptionThe < &str> {
    // If there is a value of 'Some(T)', the closure in 'map' will be executed, otherwise 'None' will be returned
    find(file_name, '. ').map(|i| &file_name[i+1..])
}
Copy the code

Some functions similar to map:

  • map_orIf the enumeration value isSome(T), runs the closure, otherwise returns the specified default value
  • map_or_elseThis function takes two closure arguments. If the enumerated value isSome(T), run the first closure, otherwise run the second.

Result

Result

is an enumerated type provided by the Rust standard library, which is defined as follows:
,>

pub enum Result<T, E> {
    Ok(T),
    Err(E),
}
Copy the code

Result

can be seen as an upgraded version of Option

. When the value is missing, Option

uses None to indicate that the value does not exist, and Result

uses Err(E) to describe error information. E in Err(E) can be either an existing type in Rust or a custom type.
,>


,>

Example: Look for the needle character in the string haystack and return the index for that character

// If not found, 'Err(& STR)' is returned, which contains error information
fn find(haystack: &str, needle: char) - >Result<usize, &str> {
    for (offset, c) in haystack.char_indices() {
        if c == needle {
            return Ok(offset); }}Err("The specified character is not found")}Copy the code

unwrap 和 expect

The unwrap function of Result

returns the value contained in Ok(T). If the enumerated value is Err(E), panic occurs.
,>

The unwrap function is generally not recommended. Instead, use pattern matching or the following functions to handle error cases:

  • unwrap_or– returnOk(T)Contains or specifies the default value.
  • unwrap_or_else– returnOk(T)Contains values that are either evaluated by a closure.
  • unwrap_or_default– returnOk(T)Contains a value or a default value of type T.

The sample; Look for the extension in the file name

fn extension_explicit(file_name: &str) - >ResultThe < &str, &str> {
    match find(file_name, '. ') {
        Err(e) => Err(e),
        Ok(i) => Ok(&file_name[i + 1. ] ),}}fn main() {
    // Pattern matching
    match extension_explicit("foo.rs") {
        Err(e) => println!("no extension: {}", e),
        Ok(ext) => assert_eq!(ext, "rs"),}// If 'Err' is encountered, panic occurs
    let extension = extension_explicit("foo.rs").unwrap();
    println!("extension: {}", extension);

    // If 'Err' is encountered, the specified default value is returned: 'rs'
    let extension = extension_explicit("foo").unwrap_or("rs");
    println!("extension: {}", extension);

    // If 'Err' is encountered, the default value of & STR is returned: empty slice
    let extension = extension_explicit("foo").unwrap_or_default();
    println!("extension: {}", extension);
}
Copy the code

The Result

expect function is similar to unwrap, which returns the value contained in Ok(T). If the enumerated value is Err(E), panic will occur, and the contents of Err(E) and expect parameters will be printed.
,>

Example:

let x: Result<u32, &str> = Err("emergency failure");
x.expect("Testing expect"); // panics with `Testing expect: emergency failure`
Copy the code

map

Like the map function for Option

, the map function for Result

simplifies pattern matching.
,>

Example:

// Use the map function instead of pattern matching, which is more concise
fn extension(file_name: &str) - >ResultThe < &str, &str> {
    // If 'Ok' is returned, the closure in 'map' will be executed, otherwise 'Err' will be returned
    find(file_name, '. ').map(|i| &file_name[i + 1..])
}
Copy the code

Result the alias

When T or E in Result

is a fixed type, we can give Result

an alias to reduce duplicate encoding.
,>
,>

Example: STD :: IO ::Result in standard library

// This is an alias for Result
      
        defined in the library 'IO' module
      ,>
// where 'STD :: IO ::Error' is a generic Error type defined in the IO module
type Result<T> = Result<T, std::io::Error>;

// Use an alias, such as in 'STD :: IO ::Read' Trait
pub trait Read {
    // 'Result
      
       ' equals' Result
       
         '
       ,>
      
    fn read(&mut self, buf: &mut [u8]) -> Result<usize>;
    fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) - >Result<usize> {... }fn is_read_vectored(&self) - >bool{... }unsafe fn initializer(&self) -> Initializer { ... }
    fn read_to_end(&mut self, buf: &mut Vec<u8- > >)Result<usize> {... }fn read_to_string(&mut self, buf: &mut String) - >Result<usize> {... }// 'Result<()>' equals' Result<(), STD :: IO ::Error> '
    fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> { ... }
    ...
}
Copy the code

Transmission error

A Propagating error in Rust is similar to a throwing exception in Java, where an error is encountered in the current function, Propagating Errors are returned and the caller of that function decides how to handle the error.

Example: Parse two strings to numeric values and return the product of the two values

use std::num::ParseIntError;

fn multiply(first_number_str: &str, second_number_str: &str) - >Result<i32, ParseIntError> {
    // An error is encountered here, and it is returned without processing, throwing the error to the upper level caller of the function
    // Parsing failed because there was a problem with the arguments passed to the function, which should be handled by the function caller
    let first_number = match first_number_str.parse::<i32> () {Ok(first_number) => first_number,
        Err(e) => return Err(e), // Use 'return' to return 'Err' ahead of time
    };

    let second_number = match second_number_str.parse::<i32> () {Ok(second_number) => second_number,
        Err(e) => return Err(e),
    };

    Ok(first_number * second_number)
}
Copy the code

Simplified delivery error

Use? Operators can simplify error passing,? The Result

operator returns a value of type T as Result

or E if an error occurs.
,>
,>

Example: Parse two strings to numeric values and return the product of the two values

use std::num::ParseIntError;

fn multiply(first_number_str: &str, second_number_str: &str) - >Result<i32, ParseIntError> {
    let first_number = first_number_str.parse::<i32> ()? ;let second_number = second_number_str.parse::<i32> ()? ;Ok(first_number * second_number)
}
Copy the code

The relevant data

The Rust Programming Language

Error Handling in Rust

Rust by Example

Rust Primer