可恢复错误与 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(())
}
复制代码
评论