组合器
在 Rust 中,组合器更多的是用于对返回结果的类型进行变换:例如使用 ok_or
将一个 Option
类型转换成 Result
类型,常见的组合器有:
or()和and():
or()
,表达式按照顺序求值,若任何一个表达式的结果是Some
或Ok
,则该值会立刻返回and()
,若两个表达式的结果都是Some
或Ok
,则第二个表达式中的值被返回。若任何一个的结果是None
或Err
,则立刻返回。
Rust 还为我们提供了 xor(亦或操作)
,但是它只能应用在 Option
上
or_else()和and_then()
跟 or()
和 and()
类似,唯一的区别在于,它们的第二个表达式是一个闭包。
map()与map_err():
map
可以将 Some
或 Ok
中的值映射为另一个
map_err可以将none或者Err中的中映射成另一个
map_or与map_or_else
map_or
在 map
的基础上提供了一个默认值.
map_or_else
与 map_or
类似,但是它是通过一个闭包来提供默认值:
ok_or与ok_or_else
他们可以将可以将 Option
类型转换为 Result
类型。其中 ok_or
接收一个默认的 Err
参数,而 ok_or_else
接收一个闭包作为 Err
参数。
filter
filter
用于对 Option
进行过滤。
fn main() {
const ERR_DEFAULT: &str = "error message";
let s1 = Some(3);
let s2 = Some(6);
let n = None;
let fn_some = || Some(6);
let fn_is_even = |x: &i8| x % 2 == 0;
let s3 = Some("abc");
let fn_character_count = |s: &str| s.chars().count();
let o: Result<&str, &str> = Ok("abc");
assert_eq!(s1.or(s2), s1); // Some1 or Some2 = Some1
assert_eq!(n.or(s1), s1); // None or Some = Some
assert_eq!(s1.filter(fn_is_even), n); // Some(3) -> 3 is not even -> None
assert_eq!(s2.filter(fn_is_even), s2); // Some(6) -> 6 is even -> Some(6)
assert_eq!(s1.or_else(fn_some), s1); // Some1 or_else Some2 = Some1
assert_eq!(s1.and_then(fn_some), s2); // Some1 and_then Some2 = Some2
assert_eq!(s3.map(fn_character_count), s1); // Some3 map = Some1
assert_eq!(s3.ok_or(ERR_DEFAULT), o); // Some(T) -> Ok(T)
}
自定义错误类型
为了更好的定义错误,Rust 在标准库中提供了一些可复用的特征,如 std::error::Error
特征
use std::fmt::{Debug, Display};
pub trait Error: Debug + Display {
fn source(&self) -> Option<&(Error + 'static)> { ... }
}
自定义错误类型只需要实现 Debug
和 Display
特征就可以作为 Err
来使用,source
方法是可选的,而 Debug
特征往往也无需手动实现,可以选择直接通过 derive
来派生。然而实现 Debug
和 Display
特征并不是作为 Err
使用的必要条件,实现这两个特征的目的是将错误打印输出。再者,实现 Debug
和 Display
特征也是为了能转换成 Error
的特征对象,而特征对象是在同一个地方使用不同类型的关键
use std::fmt;
// AppError 是自定义错误类型,它可以是当前包中定义的任何类型,
struct AppError {
code: usize,
message: String,
}
// 根据错误码显示不同的错误信息
// 为 AppError 实现 std::fmt::Display 特征
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let err_msg = match self.code {
404 => "Sorry, Can not find the Page!",
_ => "Sorry, something is wrong! Please Try Again!",
};
write!(f, "{}", err_msg)
}
}
// 为 AppError 实现 std::fmt::Debug 特征
impl fmt::Debug for AppError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"AppError {{ code: {}, message: {} }}",
self.code, self.message
)
}
}
fn produce_error() -> Result<(), AppError> {
Err(AppError {
code: 404,
message: String::from("Page not found"),
})
}
fn main() {
match produce_error() {
Err(e) => eprintln!("{}", e), // Sorry, Can not find the Page!
_ => println!("No error"),
}
eprintln!("{:?}", produce_error()); // Err(AppError { code: 404, message: Page not found })
eprintln!("{:#?}", produce_error());
// Err(
// AppError { code: 404, message: Page not found }
// )
}
例子中定义了一个错误类型,手动实现了 Display
特征后,该错误类型就可以作为 Err
来使用了。同时还手动实现了 Debug
特征,这样我们就自定义了Debug
的输出内容,而不是使用派生后系统提供的默认输出形式。
错误类型的转换
Rust 为我们提供了 std::convert::From
特征,该特征允许我们将其它的错误类型转换成自定义的错误类型
pub trait From<T>: Sized {
fn from(_: T) -> Self;
}
为自定义类型AppError实现 From
特征:
use std::fs::File;
use std::io;
#[derive(Debug)]
struct AppError {
kind: String, // 错误类型
message: String, // 错误信息
}
// 为 AppError 实现 std::convert::From 特征,由于 From 包含在 std::prelude 中,因此可以直接简化引入。
// 实现 From<io::Error> 意味着我们可以将 io::Error 错误转换成自定义的 AppError 错误
impl From<io::Error> for AppError {
fn from(error: io::Error) -> Self {
AppError {
kind: String::from("io"),
message: error.to_string(),
}
}
}
fn main() -> Result<(), AppError> {
let _file = File::open("nonexistent_file.txt")?;
Ok(())
}
// --------------- 上述代码运行后输出 ---------------
Error: AppError { kind: "io", message: "No such file or directory (os error 2)" }
File::open
返回的是 std::io::Error
, 我们并没有进行任何显式的转换,它就能自动变成 AppError
,这是因为 ?
将错误进行隐式的强制转换
归一化不同的错误类型
在一个函数中有时需要返回不同的错误,而一个函数的返回值只有一种错误类型,为了解决此问题,需要将不同的错误类型归一化为同一种错误类型。要实现这个目的有三种方式:
- 使用特征对象
Box<dyn Error>
- 自定义错误类型
- 使用
thiserror
Box<dyn Error>
使用该方法的前提是错误类型能转换成 Error
的特征对象:
use std::fs::read_to_string;
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
let html = render()?;
println!("{}", html);
Ok(())
}
fn render() -> Result<String, Box<dyn Error>> {
let file = std::env::var("MARKDOWN")?;
let source = read_to_string(file)?;
Ok(source)
}
自定义返回的错误类型
对于没有实现Error
特征的类型,就无法使用Box<dyn Error>, 此时我们就需要自定义一个要返回的的错误类型,他要实现Display+Debug+From特征,相对来说会比较麻烦,但非常灵活
use std::fs::read_to_string;
fn main() -> Result<(), MyError> {
let html = render()?;
println!("{}", html);
Ok(())
}
fn render() -> Result<String, MyError> {
let file = std::env::var("MARKDOWN")?;
let source = read_to_string(file)?;
Ok(source)
}
#[derive(Debug)]
enum MyError {
EnvironmentVariableNotFound,
IOError(std::io::Error),
}
impl From<std::env::VarError> for MyError {
fn from(_: std::env::VarError) -> Self {
Self::EnvironmentVariableNotFound
}
}
impl From<std::io::Error> for MyError { //只有为自定义错误类型实现 Error 特征后,才能转换成
//相应的特征对象。
fn from(value: std::io::Error) -> Self {
Self::IOError(value)
}
}
impl std::error::Error for MyError {}
impl std::fmt::Display for MyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MyError::EnvironmentVariableNotFound => write!(f, "Environment variable not found"),
MyError::IOError(err) => write!(f, "IO Error: {}", err.to_string()),
}
}
}
使用 thiserror
thiserror可以帮助我们简化自定义错误类型带来的代码冗长,只要简单写写注释,就可以实现错误处理了,代码结构上非常简洁
use std::fs::read_to_string;
fn main() -> Result<(), MyError> {
let html = render()?;
println!("{}", html);
Ok(())
}
fn render() -> Result<String, MyError> {
let file = std::env::var("MARKDOWN")?;
let source = read_to_string(file)?;
Ok(source)
}
#[derive(thiserror::Error, Debug)]
enum MyError {
#[error("Environment variable not found")]
EnvironmentVariableNotFound(#[from] std::env::VarError),
#[error(transparent)]
IOError(#[from] std::io::Error),
}
还有更简洁的是anyhow ,它和 thiserror
是同一个作者开发的,两者如何选用,就看开发者是否关注自定义错误消息,关注则使用 thiserror
(常见业务代码),否则使用 anyhow
(编写第三方库代码)。还有一个error-chain 也是简单好用的库
use std::fs::read_to_string;
use anyhow::Result;
fn main() -> Result<()> {
let html = render()?;
println!("{}", html);
Ok(())
}
fn render() -> Result<String> {
let file = std::env::var("MARKDOWN")?;
let source = read_to_string(file)?;
Ok(source)
}