Rust 错误处理

在编程语言中,处理错误的方式大致可分为两种:

  • 抛出异常
  • 作为值返回(返回一个错误代码或者自定义错误类型)

Rust 将错误作为值返回,并提供了原生的错误处理方案。在 Rust 中,错误可分为两种:

  • 可恢复错误(recoverable)—— Result<T, E>
  • 不可恢复错误(unrecoverable)—— panic!

panic!

panic! 是 Rust 提供的一个宏,这个宏在执行时会打印错误信息,展开并清理栈数据。

panic! 会导致当前线程结束,甚至是整个程序的结束。因此,panic! 通常适合用在编写的示例或者测试代码中。

示例:直接调用 panic!

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

当出现 panic! 时,程序默认会展开(unwinding),即:程序会回溯栈并清理它遇到的每个函数的数据。另外,也可以选择直接终止(abort),不清理数据就退出程序,占用的内存需要操作系统来清理。如果要选择直接终止,需要在 Cargo.toml 文件中添加下面的配置:

# 在这样的配置下,项目生成的二进制文件会比较小。
[profile.release]
panic = 'abort'

栈回溯信息

当程序遇到 panic! 退出时,它会输出错误信息(即:panic!() 括号里的内容),以及发生错误的位置。并且它会提示你如何查看栈回溯(backtrace)信息。

示例:制造一个不可恢复错误

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

    v[99];
}

运行上面的代码:

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>

输出信息提示我们可以设置 RUST_BACKTRACE 环境变量来显示栈回溯信息。按照提示,重新运行程序,我们会看到详细的栈回溯信息:

PS D:\vscode\rust_demo\hello> $Env:RUST_BACKTRACE=1
PS D:\vscode\rust_demo\hello>
PS D:\vscode\rust_demo\hello> cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     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
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:227
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>

上面的示例是在 Windows 系统中运行的,设置环境变量的方式与 Linux 不同。若使用 CMD ,则执行 set RUST_BACKTRACE=1 命令。若使用 PowerShell ,则执行 $Env:RUST_BACKTRACE=1命令。

Option

Option<T> 是 Rust 标准库提供的一个枚举类型,它只有两个成员:

  • Some(T) – 包含一个 T 类型的值
  • None – 表示没有值

当使用 Option<T> 作为函数的参数或者返回值类型时,函数的创建者或调用者必须处理枚举值是 None 的情况,这样可以减少潜在的 bug。

示例:在字符串 haystack 中查找 needle 字符,并返回该字符的索引

// 返回值是 `Option<usize>`,用 `None` 来表示没有找到 needle 字符
fn find(haystack: &str, needle: char) -> Option<usize> {
    for (offset, c) in haystack.char_indices() {
        if c == needle {
            return Some(offset);
        }
    }
    None
}

unwrap

Option<T>unwrap 函数返回 Some(T) 中的 T 类型值。若枚举值是 None,则会 panic。因此,该函数一般不被推荐使用。应该使用模式匹配对 None 值进行处理,或者使用下面三个函数:

  • unwrap_or – 返回 Some(T) 包含的值或者返回指定的默认值。
  • unwrap_or_else – 返回 Some(T) 包含的值或者通过一个闭包计算。
  • unwrap_or_default – 返回 Some(T) 包含的值或者 T 类型的默认值。

示例:查找文件名中的扩展名

fn extension_explicit(file_name: &str) -> Option<&str> {
    // 模式匹配
    match find(file_name, '.') {
        None => None,
        Some(i) => Some(&file_name[i+1..]),
    }
}
fn main() {
    // 模式匹配
    match extension_explicit("foo.rs") {
        None => println!("no extension"),
        Some(ext) => assert_eq!(ext, "rs"),
    }

    // 若遇到 `None`,则直接 panic
    let extension = extension_explicit("foo.rs").unwrap();
    println!("extension: {}", extension);

    // 若遇到 `None`,则返回指定的默认值:`rs`
    let extension = extension_explicit("foo").unwrap_or("rs");
    println!("extension: {}", extension);

    // 若遇到 `None`,则返回 &str 的默认值
    let extension = extension_explicit("foo").unwrap_or_default();
    println!("extension: {}", extension);
}

map

Option<T>map 函数用来将一个枚举类型 Option<T> 映射为另外一个枚举类型 Option<U>

map 的参数是一个闭包,用来指明如何映射。

示例:查找文件名中的扩展名

// 使用 `map` 函数可以替代模式匹配,这种方式更简洁
fn extension(file_name: &str) -> Option<&str> {
    // 若有值 `Some(T)` 则会执行 `map` 里的闭包,否则直接返回 `None`
    find(file_name, '.').map(|i| &file_name[i+1..])
}

map 类似的一些函数:

  • map_or – 若枚举值是 Some(T),则运行闭包,否则返回指定的默认值
  • map_or_else – 该函数有两个闭包参数。若枚举值是 Some(T),则运行第一个闭包,否则运行第二个闭包。

Result

Result<T, E> 是 Rust 标准库提供的一个枚举类型,其定义如下:

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

Result<T, E> 可以看做是 Option<T> 的升级版本,当值缺失时,Option<T> 使用 None 表示值不存在,而 Result<T, E> 使用 Err(E) 描述错误信息。Err(E) 中的 E 既可以是 Rust 中现有的类型,也可以是我们自定义的类型。

示例:在字符串 haystack 中查找 needle 字符,并返回该字符的索引

// 若未找到则返回 `Err(&str)` ,其中包含错误信息
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")
}

unwrapexpect

Result<T, E>unwrap 函数返回 Ok(T) 包含的值。若枚举值是 Err(E),则会 panic。

unwrap 函数通常不推荐使用,应该使用模式匹配或者下面的函数来处理报错情况:

  • unwrap_or – 返回 Ok(T) 包含的值或者指定的默认值。
  • unwrap_or_else – 返回 Ok(T) 包含的值或者通过一个闭包计算。
  • unwrap_or_default – 返回 Ok(T) 包含的值或者 T 类型的默认值。

示例;查找文件名中的扩展名

fn extension_explicit(file_name: &str) -> Result<&str, &str> {
    match find(file_name, '.') {
        Err(e) => Err(e),
        Ok(i) => Ok(&file_name[i + 1..]),
    }
}
fn main() {
    // 模式匹配
    match extension_explicit("foo.rs") {
        Err(e) => println!("no extension: {}", e),
        Ok(ext) => assert_eq!(ext, "rs"),
    }

    // 若遇到 `Err`,则直接 panic
    let extension = extension_explicit("foo.rs").unwrap();
    println!("extension: {}", extension);

    // 若遇到 `Err`,则返回指定的默认值:`rs`
    let extension = extension_explicit("foo").unwrap_or("rs");
    println!("extension: {}", extension);

    // 若遇到 `Err`,则返回 &str 的默认值:空切片
    let extension = extension_explicit("foo").unwrap_or_default();
    println!("extension: {}", extension);
}

Result<T, E>expect 函数与 unwrap 类似,该函数返回 Ok(T) 包含的值。若枚举值是 Err(E),则会 panic ,除了会输出 Err(E) 中的内容,还会输出 expect 参数内容。

示例

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

map

Option<T>map 函数一样,Result<T, E>map 函数可以简化模式匹配。

示例

// 使用 map 函数替代模式匹配,这种方式更简洁
fn extension(file_name: &str) -> Result<&str, &str> {
    // 若返回值是 `Ok` 则会执行 `map` 里的闭包,否则直接返回 `Err`
    find(file_name, '.').map(|i| &file_name[i + 1..])
}

Result 别名

Result<T, E> 中的 T 或者 E 是固定的类型时,我们可以给 Result<T, E> 取一个别名,这样可以减少重复编码。

示例:标准库中的 std::io::Result

// 这是标准库 `io` 模块中给 `Result<T, std::io::Error>` 定义的一个别名
// 其中 `std::io::Error` 是 io 模块中定义的一个通用的错误类型
type Result<T> = Result<T, std::io::Error>;

// 使用别名,比如在 `std::io::Read` Trait 中
pub trait Read {
    // `Result<usize>` 等同于 `Result<usize, std::io::Error>`
    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<()>` 等同于 `Result<(), std::io::Error>`
    fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> { ... }
    ...
}

传递错误

Rust 中的传递错误(Propagating Errors)类似于 Java 中的抛出异常,就是在当前函数中遇到错误时不做处理,而是将这个错误返回,让该函数的调用者决定如何处理该错误。

示例:将两个字符串解析为数值,并返回两个数值的乘积

use std::num::ParseIntError;

fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> {
    // 此处遇到错误,不做处理直接返回,把错误抛给上层该函数的调用者
    // 解析出错是因为传入该函数的参数有问题,应该由函数的调用者来处理
    let first_number = match first_number_str.parse::<i32>() {
        Ok(first_number) => first_number,
        Err(e) => return Err(e), // 这里使用 `return` 提前返回 `Err`
    };

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

    Ok(first_number * second_number)
}

简化传递错误

使用 ? 操作符可以简化错误的传递,? 操作符的作用是:返回 Result<T, E>T 类型的值,若遇到错误则返回 E

示例:将两个字符串解析为数值,并返回两个数值的乘积

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

相关资料

The Rust Programming Language

Error Handling in Rust

Rust by Example

Rust Primer

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值