写点什么

5 分钟速读之 Rust 权威指南(十六)

用户头像
码生笔谈
关注
发布于: 2021 年 06 月 03 日
5分钟速读之Rust权威指南(十六)

可恢复错误与 Result

一些没有严重到影响程序退出的错误属于可恢复错误,比如因为文件不存在导致打开文件失败时,应该考虑创建文件而不是终止进程。


在 rust 中使用内置枚举 Result 来处理这种错误:


enum Result<T, E> {  Ok(T),  Err(E)}
复制代码


例如打开一个存在的文件:


// 引入文件操作模块use std::fs::File;
// 打开项目中的配置文件let result = File::open("./Cargo.toml");
// 匹配打开的结果let file = match result { // 与Option一样,Ok和Err可以直接使用 // 如果是OK类型,将f绑定给file Ok(f) => f, // 如果是Err类型,则主动触发报错 Err(err) => panic!("打开文件失败:{:?}", err)};
println!("{:?}", file)// File { fd: 3, path: "/Cargo.toml", read: true, write: false }
复制代码


当文件不存在时:


// 打开一个不存在的文件let result = File::open("./Cargo2.toml");let file = match result {  Ok(f) => f,  Err(err) => panic!("打开文件失败:{:?}", err)};
println!("{:?}", file)// thread 'main' panicked at '打开文件失败:Os { code: 2, kind: NotFound, message: "No such file or directory" }'
复制代码

匹配不同错误

根据错误类型进行处理,打开文件不存在时,我们则创建一个,如果是因为没有权限,则再抛出错误:


use std::fs::File;
// 引如错误类型枚举use std::io::ErrorKind;
let result = File::open("./Cargo2.toml");let file = match result { Result::Ok(file) => file, // 匹配错误类型 Result::Err(err) => match err.kind() { // 如果文件未找到,那么创建文件 ErrorKind::NotFound => match File::create("./Cargo2.toml") { Ok(fc) => fc, Err(e) => panic!("创建文件失败:{:?}", e), }, // 其他错误类型则不处理 _ => panic!("打开文件失败:{:?}", err), },};println!("{:?}", file)
复制代码


利用闭包简化嵌套 match 表达式:


// 使用map_err方法,当result是Err时会执行闭包中的代码let file = File::open("./Cargo2.toml").map_err(|error| {  if error.kind() == ErrorKind::NotFound {    // unwrap_or_else用于当result是Err时通过闭包计算默认值    File::create("./Cargo2.toml").unwrap_or_else(|error| {      panic!("创建文件失败:{:?}", error)    })  } else {    panic!("打开文件失败:{:?}", error)  }})println!("{:?}", file)
复制代码

unwrap 和 expect

当 Result 的返回值是 Ok 变体时,unwrap 就会返回 Ok 内部的值。而当 Result 的返回值是 Err 变体时,unwrap 则会替我们调用 panic!:


// 遇到错误直接抛出let file = File::open("./Cargo3.toml").unwrap();// expect可以自定义错误信息let file = File::open("./Cargo3.toml").expect("出错了");
复制代码

传播错误

使用传播错误的可以让调用者来控制如何处理错误:


use std::fs::File;use std::io::{ self, Read };
fn read_username_from_file() -> Result<String, io::Error> { let mut file = match File::open("./Cargo.toml") { Result::Ok(file) => file, // 如果读取失败则把错误信息返回,交给调用者处理 Result::Err(e) => return Result::Err(e), }; let mut str = String::new(); match file.read_to_string(&mut str) { Result::Ok(_) => Result::Ok(str), // 这里也同样交给调用者处理,由于match是最后一个语句,所以这里不用加return Result::Err(e) => Result::Err(e), }}
复制代码


传播错误的模式在 Rust 编程中经常使用,所以 rust 专门提供了一个问号运算符(?)来简化它的语法:


fn read_username_from_file() -> Result<String, io::Error> {  let mut file = File::open("./Cargo.toml")?;  let mut str = String::new();  file.read_to_string(&mut str)?;  Result::Ok(str)}// 问号意味着如果处理正常,则当前表达式返回处理结果,// 如果报错,则将表达式所在的函数结束执行并返回Result::Err错误信息。
复制代码


问号还支持链式调用,这个语法在 JS 中也有哦:


fn read_username_from_file() -> Result<String, io::Error> {  let mut str = String::new();  File::open("./Cargo.toml")?.read_to_string(&mut str)?;  Result::Ok(str)}
复制代码


从文件中读取字符串是一种常见的操作,所以 rust 提供了一个函数 fs::read_to_string,用于打开文件,创建一个新 String,并将文件中的内容读入这个 String,接着返回给函数调用者:


use std::fs;fn read_username_from_file() -> Result<String, io::Error> {  fs::read_to_string("./Cargo2.toml")}
复制代码


问号有一个限制,只能用于返回 Result 类型的函数,由于?是通过 match 处理 Result 的语法糖,当处理的是 Result::Err 类型时,由于?会把错误信息返回,这就要求函数的返回值必须是 Result::Err 类型:


  // 在main函数中使用问号将报错,因为main函数的返回值不是Result
| | fn main() { | | File::open("./Cargo2.toml")? | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot use the `?` operator in a function that returns `()` | | } | |_- this function should return `Result` or `Option` to accept `?` | = help: the trait `Try` is not implemented for `()` = note: required by `from_error`
复制代码


不过有一种办法可以在 main 函数中使用问号:


// 这里的Box<dyn Error>被称作trait对象,将在后边介绍fn main() -> Result<(), Box<dyn Error>>{  File::open("./Cargo2.toml")?;  Result::Ok(())}
复制代码


发布于: 2021 年 06 月 03 日阅读数: 8
用户头像

码生笔谈

关注

欢迎关注「码生笔谈」公众号 2018.09.09 加入

前端、Rust干货分享,一起成为更好的Javascripter & Rustacean

评论

发布
暂无评论
5分钟速读之Rust权威指南(十六)