rust学习之错误处理

Rust 中的错误主要分为可恢复错误与不可恢复错误。Result<T, E> 用于可恢复错误,panic! 用于不可恢复错误。panic可分为被动触发和主动调用,被动触发指的时由于代码本身原因发生致命错误,如数组访问越界。主动触发是调用panic! 宏,当调用执行该宏时,序会打印出一个错误信息,展开报错点往前的函数调用堆栈,最后退出程序。

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

当出现 panic! 时,程序提供了栈展开与直接终止两种方式来处理终止流程,默认的方式就是 栈展开,使用直接终止的方式,修改 Cargo.toml 文件,

[profile.release]

panic = 'abort'

Result<T, E> 枚举类型用来表示函数的返回结果。泛型参数 T 代表成功时存入的正确值的类型,存放方式是 Ok(T)E 代表错误时存入的错误值,存放方式是 Err(E)。

enum Result<T, E> {

Ok(T),

Err(E),

}

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

 对打开文件后的 Result<T, E> 类型进行匹配取值,如果是成功,则将 Ok(file) 中存放的的文件句柄 file 赋值给 f,如果失败,且是文件不存在错误 ErrorKind::NotFound,就创建文件,这里创建文件File::create 也是返回 Result,因此继续用 match 对其结果进行处理:创建成功,将新的文件句柄赋值给 f,如果失败,则 panic,其他的错误,一律 panic。

对于返回结果Result,除了使用match匹配处理,还可以使用unwrap 和 expect。它们的作用就是,如果返回成功,就将 Ok(T) 中的值取出来,如果失败,就直接 panic。expect 跟 unwrap 很像,也是遇到错误直接 panic, 但是会带上自定义的错误提示信息,相比之下,expect 相比 unwrap 能提供更精确的错误信息。

use std::fs::File;

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

如果调用这段代码时 hello.txt 文件不存在,那么 unwrap 就将直接 panic,expect则会带上错误提示信息panic。

unwrap_or_else 是定义在 Result<T,E> 上的常用方法,如果 Result 是 Ok,那该方法就类似 unwrap:返回 Ok 内部的值;如果是 Err,就调用闭包中的自定义代码对错误进行进一步处理

// 对 build 返回的 `Result` 进行处理
    let config = Config::build(&args).unwrap_or_else(|err| {
        println!("Problem parsing arguments: {err}");
        process::exit(1);
    });

在实际处理错误的情景中,有时需要将错误层层传递,其中?H宏可以使代码简化

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

其中? 的作用跟上面的 match 几乎一模一样,如果结果是 Ok(T),则把 T 赋值给 f,如果结果是 Err(E),则返回该错误,所以 ? 特别适合用来传播错误。

let mut f = match f {
    // 打开文件成功,将file句柄赋值给f
    Ok(file) => file,
    // 打开文件失败,将错误返回(向上传播)
    Err(e) => return Err(e),
};

?不仅可以自动进行类型转换,只要函数返回的错误 ReturnError 实现了 From<OtherError> 特征,那么 ? 就会自动把 OtherError 转换为 ReturnError? 还能实现链式调用。例如,上面代码还可以简化写成:

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

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

File::open 遇到错误就返回,没有错误就将 Ok 中的值取出来用于下一个方法调用;当然,还可以进一步简化,也是最常用的操作方式:

use std::fs;
use std::io;

fn read_username_from_file() -> Result<String, io::Error> {
    // read_to_string是定义在std::io中的方法,因此需要在上面进行引用
    fs::read_to_string("hello.txt")
}

Rust 标准库为我们提供了 fs::read_to_string 函数,该函数内部会打开一个文件、创建 String、读取文件内容最后写入字符串并返回。

?也用于Option枚举的返回。Result 通过 ? 返回错误, Option 就通过 ? 返回 None,? 操作符需要一个变量来承载正确的值,只有错误值能直接返回,正确的值不行,因此 ? 只能用于以下形式:

  • let v = xxx()?;
  • xxx()?.yyy()?;

pub enum Option<T> {
    Some(T),
    None
}

fn last_char_of_first_line(text: &str) -> Option<char> {
    text.lines().next()?.chars().last()
}

.next 方法返回的是 Option 类型:如果返回 Some(&str),那么继续调用 chars 方法,如果返回 None,则直接从整个函数中返回 None,不再继续进行链式调用。

fn open_file() -> Result<File, Box<dyn std::error::Error>> {
    let mut f = File::open("hello.txt")?;
    Ok(f)
}

File::open 报错时返回的错误是 std::io::Error 类型,但是 open_file 函数返回的错误类型是 std::error::Error 的特征对象,可以看到一个错误类型通过 ? 返回后,变成了另一个错误类型。根本原因是在于标准库中定义的 From 特征,该特征有一个方法 from,用于把一个类型转成另外一个类型,? 可以自动调用该方法,然后进行隐式类型转换。Box<dyn std::error::Error>是一个Error 的特征对象,它表示函数返回一个类型,该类型实现了 Error 特征,这样我们就无需指定具体的错误类型,其它函数也可以返回这个特征对象。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值