Rust 学习 09

archive time: 2022-05-23

今天来看看错误处理

错误处理

按照是否可以恢复, Rust 里的错误可以分为 可恢复不可恢复 两种

对应于 Option<T>, Result<T, E>panic!

panic!

panic! 应该算是比较常见的错误了, 而且是不可恢复的错误, 数组越界什么的就会引起 panic!

如果遇到 panic! 宏:

  • 你的程序会打印一个错误信息
  • 展开 (unwind) 和 清理调用栈 (Stack)
  • 退出程序

当然, 我们可以在 [profile] 里设置展开或是中止 (abort) 调用栈

默认情况会展开调用栈, 清理使用内存, 工作量很大

但是如果不展开, 使用的内存需要操作系统来清理

# Cargo.toml
# ...
[profile.release]
panic = "abort"

如果想要更加详细的回溯信息, 可以通过设置 RUST_BACKTRACR 变量来实现

powershell 下使用 $env:RUST_BACKTRACE=1 来启用

Result<T, E>

不是所有的错误都需要中断程序来解决, 对于其中的一些错误, 我们可以使用 Result<T, E> 这个枚举类型来处理

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

T 对应操作成功的返回值, E 对应错误的类型, 可以使用 match 来区分

// 尝试打开文件, 打不开就尝试创建
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!("Error creating file: {:#?}", e),
            },
            oe => panic!("Error opening the file: {:#?}", oe),
        },
    };
    println!("{:#?}", f);
}
/*
File {
    handle: 0x00000000000000f0,
    path: "\\?\\C:\\Users\\kands\\Documents\\projs\\Drafts\rust\\learn_rust\\hello.txt",
}
*/

对于上述代码, 我们可以使用 Closure 来改良

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let f = File::open("hello.txt").unwrap_or_else(|err| {
        if err.kind() == ErrorKind::NotFound {
            File::create("hello.txt").unwrap_or_else(|error| {
                panic!("Error creating file: {:#?}", error);
            })
        } else {
            panic!("Error opening the file: {:#?}", err);
        }
    });
    println!("{:#?}", f);
}

不过具体使用哪种风格是取决于你的习惯

处理 Result<T, E> 还有 unwrap()expect() 两个方法

第一个方法默认会成功, 第二个是可以自定义错误信息, 成功都会返回正确内容

传播错误

传播错误, 也就是返回 Option<T> 或者 Result<T, E> 类型, 来让调用者知道和处理错误

在 Rust 里, 还可以使用 ? 操作符来实现

fn read_file_by_name(name: &str) -> Result<String, std::io::Error> {
    let mut f = File::open(name)?; // if error, return Err
    let mut s = String::new();
    f.read_to_string(&mut s)?; // same
    Ok(s)
}

但是这里需要注意匹配错误类型, 如果错误类型不匹配, Rust 会调用相应错误下的 from() 函数来转化

Trait std::convert::Fromfrom() 函数

由于 ? 会返回正确执行时的值, 所以我们还可以进一步简化

fn read_file_by_name(name: &str) -> Result<String, std::io::Error> {
    let mut s = String::new();
    File::open(name)?.read_to_string(&mut s)?;
    Ok(s)
}

但是注意 ? 只能作用于返回值为 Option 或 Result 类型的函数, 或者实现了 Trait Try 的类型, 因为 ? 会调用 from_error 函数

Box<dyn Error> 表示所有可能的错误类型

何时使用

在演示程序或者一些原型代码, 测试代码时可以使用 panic!() 或者 unwrap(), expect() 等方法来发送 panic

而日常代码, 如果代码可能会 破坏整个程序不是预期情况, 那么建议使用 panic!(), 否则最好使用 Result<T, E> 或者 Option<T>


好, 今天就学到这里罢, 刷 Codewars 去了

泛型那部分就摸了, 很简单的看看文档就懂了