错误处理

1.panic!与不可恢复错误

panic! 宏执行时,程序会打印出一个错误信息,展开并清理栈数据,然后接着退出。

对应 panic 时的栈展开或终止

当出现 panic 时,程序会默认开始 展开(unwinding),意味着 Rust回溯栈 并清理遇到的每一个函数的数据,不过这个回溯并清理的过程有很多工作。另一种选择时直接 终止(abort),这会不清理数据就退出程序。那么程序所使用的内存需要由操作系统来清理。

可在 Cargo.toml 中的 [profile] 部分增加 panic = ‘abort’,可以由展开切换为终止。也可在 release 模式下发生 panic 时直接终止:

[profile.release]
panic = 'abort'

2.Result 与可恢复错误

Result 枚举,它定义有如下两个成员,OkErr

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

TE 是泛型类型参数,T 代表成功时返回的 Ok 成员中的数据的类型,而 E 代表失败时返回的 Err 成员中的错误的类型。

use std::fs::File;

fn main() {
    let f = File::open("hello.txt");
}

打开文件

use std::fs::File;

fn main() {
    let f = File::open("hello.txt");

    let f = match f {
        Ok(file) => file,
        Err(error) => {
            panic!("Problem opening the file: {:?}", error)
        },
    };
}

使用 match 表达式处理可能会返回的 Result 成员

2.1 匹配不同错误

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!("Problem creating the file: {:?}", e),
            },
            other_error => panic!("Problem opening the file: {:?}", other_error),
        },
    };
}

使用不同的方式处理不同类型的错误

2.2 unwrap 和 expect

Result<T, E> 类型定义了很多辅助方法来处理各种情况。其中之一叫做 unwrap,如果 Result 值是成员 Okunwrap 会返回 Ok 中的值。如果 Result 是成员 Errunwrap 会为我们调用 panic!

use std::fs::File;

fn main() {
    let f = File::open("hello.txt").unwrap();
}

还有另一个类似于 unwrap 的方法,它还允许我们选择 panic! 的错误信息:expect。使用 expect 而不是 unwrap 并提供一个好的错误信息可以表明你的意图并更易于追踪 panic 的根源。

use std::fs::File;

fn main() {
    let f = File::open("hello.txt").expect("Failed to open hello.txt");
}

2.3 传播错误

当编写一个需要先调用一些可能会失败的操作的函数时,除了在这个函数中处理错误外,还可以选择让调用者知道这个错误并决定该如何处理。这被称为 传播(propagating)错误,这样能更好地控制代码调用,因为比起你代码所拥有的上下文,调用者可能拥有更多信息或逻辑来决定应该如何处理错误。

use std::io;
use std::io::Read;
use std::fs::File;

fn read_username_from_file() -> Result<String, io::Error> {
    let f = File::open("hello.txt");

    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut s = String::new();

    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}
use std::io;
use std::io::Read;
use std::fs::File;

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

一个使用 ? 运算符向调用者返回错误的函数

? 运算符消除了大量样板代码并使得函数的实现更简单。我们甚至可以在 ? 之后直接使用链式方法调用来进一步缩短代码。

use std::io;
use std::io::Read;
use std::fs::File;

fn read_username_from_file() -> Result<String, io::Error> {
    let mut s = String::new();

    File::open("hello.txt")?.read_to_string(&mut s)?;

    Ok(s)
}

3.何时 panic!

最后更新于