杭州模板做网站,鹿泉网络推广,wordpress添加百度地图,免费素材库短视频素材网站文章目录 错误处理组合器or() 和 and()or_else() 和 and_then()filtermap() 和 map_err()map_or() 和 map_or_else()ok_or() and ok_or_else() 自定义错误类型错误转换 From 特征 归一化不同的错误类型Boxdyn Error自定义错误类型 简化错误处理thiserroranyhow 错误处理… 文章目录 错误处理组合器or() 和 and()or_else() 和 and_then()filtermap() 和 map_err()map_or() 和 map_or_else()ok_or() and ok_or_else() 自定义错误类型错误转换 From 特征 归一化不同的错误类型Boxdyn Error自定义错误类型 简化错误处理thiserroranyhow 错误处理
组合器
与组合器模式有所不同在 Rust 中组合器更多的是用于对返回结果的类型进行变换例如使用 ok_or 将一个 Option 类型转换成 Result 类型。
or() 和 and()
跟布尔关系的与/或很像这两个方法会对两个表达式做逻辑组合最终返回 Option / Result。
or()表达式按照顺序求值若任何一个表达式的结果是 Some 或 Ok则该值会立刻返回and()若两个表达式的结果都是 Some 或 Ok则第二个表达式中的值被返回。若任何一个的结果是 None 或 Err 则立刻返回。 实际上只要将布尔表达式的 true / false替换成 Some / None 或 Ok / Err 就很好理解了。
fn main() {let s1 Some(some1);let s2 Some(some2);let n: Optionstr None;let o1: Resultstr, str Ok(ok1);let o2: Resultstr, str Ok(ok2);let e1: Resultstr, str Err(error1);let e2: Resultstr, str Err(error2);assert_eq!(s1.or(s2), s1); // Some1 or Some2 Some1assert_eq!(s1.or(n), s1); // Some or None Someassert_eq!(n.or(s1), s1); // None or Some Someassert_eq!(n.or(n), n); // None1 or None2 None2assert_eq!(o1.or(o2), o1); // Ok1 or Ok2 Ok1assert_eq!(o1.or(e1), o1); // Ok or Err Okassert_eq!(e1.or(o1), o1); // Err or Ok Okassert_eq!(e1.or(e2), e2); // Err1 or Err2 Err2assert_eq!(s1.and(s2), s2); // Some1 and Some2 Some2assert_eq!(s1.and(n), n); // Some and None Noneassert_eq!(n.and(s1), n); // None and Some Noneassert_eq!(n.and(n), n); // None1 and None2 None1assert_eq!(o1.and(o2), o2); // Ok1 and Ok2 Ok2assert_eq!(o1.and(e1), e1); // Ok and Err Errassert_eq!(e1.and(o1), e1); // Err and Ok Errassert_eq!(e1.and(e2), e1); // Err1 and Err2 Err1
}除了 or 和 and 之外Rust 还为我们提供了 xor 但是它只能应用在 Option 上.
or_else() 和 and_then()
它们跟 or() 和 and() 类似唯一的区别在于它们的第二个表达式是一个闭包。
fn main() {// or_else with Optionlet s1 Some(some1);let s2 Some(some2);let fn_some || Some(some2); // 类似于: let fn_some || - Optionstr { Some(some2) };let n: Optionstr None;let fn_none || None;assert_eq!(s1.or_else(fn_some), s1); // Some1 or_else Some2 Some1assert_eq!(s1.or_else(fn_none), s1); // Some or_else None Someassert_eq!(n.or_else(fn_some), s2); // None or_else Some Someassert_eq!(n.or_else(fn_none), None); // None1 or_else None2 None2// or_else with Resultlet o1: Resultstr, str Ok(ok1);let o2: Resultstr, str Ok(ok2);let fn_ok |_| Ok(ok2); // 类似于: let fn_ok |_| - Resultstr, str { Ok(ok2) };let e1: Resultstr, str Err(error1);let e2: Resultstr, str Err(error2);let fn_err |_| Err(error2);assert_eq!(o1.or_else(fn_ok), o1); // Ok1 or_else Ok2 Ok1assert_eq!(o1.or_else(fn_err), o1); // Ok or_else Err Okassert_eq!(e1.or_else(fn_ok), o2); // Err or_else Ok Okassert_eq!(e1.or_else(fn_err), e2); // Err1 or_else Err2 Err2
}
fn main() {// and_then with Optionlet s1 Some(some1);let s2 Some(some2);let fn_some |_| Some(some2); // 类似于: let fn_some |_| - Optionstr { Some(some2) };let n: Optionstr None;let fn_none |_| None;assert_eq!(s1.and_then(fn_some), s2); // Some1 and_then Some2 Some2assert_eq!(s1.and_then(fn_none), n); // Some and_then None Noneassert_eq!(n.and_then(fn_some), n); // None and_then Some Noneassert_eq!(n.and_then(fn_none), n); // None1 and_then None2 None1// and_then with Resultlet o1: Resultstr, str Ok(ok1);let o2: Resultstr, str Ok(ok2);let fn_ok |_| Ok(ok2); // 类似于: let fn_ok |_| - Resultstr, str { Ok(ok2) };let e1: Resultstr, str Err(error1);let e2: Resultstr, str Err(error2);let fn_err |_| Err(error2);assert_eq!(o1.and_then(fn_ok), o2); // Ok1 and_then Ok2 Ok2assert_eq!(o1.and_then(fn_err), e2); // Ok and_then Err Errassert_eq!(e1.and_then(fn_ok), e1); // Err and_then Ok Errassert_eq!(e1.and_then(fn_err), e1); // Err1 and_then Err2 Err1
}
filter
filter 用于对 Option 进行过滤
fn main() {let s1 Some(3);let s2 Some(6);let n None;let fn_is_even |x: i8| x % 2 0;assert_eq!(s1.filter(fn_is_even), n); // Some(3) - 3 is not even - Noneassert_eq!(s2.filter(fn_is_even), s2); // Some(6) - 6 is even - Some(6)assert_eq!(n.filter(fn_is_even), n); // None - no value - None
}map() 和 map_err()
map 可以将 Some 或 Ok 中的值映射为另一个同类型的值
fn main() {let s1 Some(abcde);let s2 Some(5);let n1: Optionstr None;let n2: Optionusize None;let o1: Resultstr, str Ok(abcde);let o2: Resultusize, str Ok(5);let e1: Resultstr, str Err(abcde);let e2: Resultusize, str Err(abcde);
// 统计字符串中let fn_character_count |s: str| s.chars().count();assert_eq!(s1.map(fn_character_count), s2); // Some1 map Some2assert_eq!(n1.map(fn_character_count), n2); // None1 map None2assert_eq!(o1.map(fn_character_count), o2); // Ok1 map Ok2assert_eq!(e1.map(fn_character_count), e2); // Err1 map Err2
}但是如果你想要将 Err 中的值进行改变此时我们需要用 map_err
fn main() {let o1: Resultstr, str Ok(abcde);let o2: Resultstr, isize Ok(abcde);let e1: Resultstr, str Err(404);let e2: Resultstr, isize Err(404);let fn_character_count |s: str| - isize { s.parse().unwrap() }; // 该函数返回一个 isizeassert_eq!(o1.map_err(fn_character_count), o2); // Ok1 map Ok2assert_eq!(e1.map_err(fn_character_count), e2); // Err1 map Err2
}map_or() 和 map_or_else()
map_or 在 map 的基础上提供了一个默认值:
fn main() {const V_DEFAULT: u32 1;let s: Resultu32, () Ok(10);let n: Optionu32 None;let fn_closure |v: u32| v 2;assert_eq!(s.map_or(V_DEFAULT, fn_closure), 12);assert_eq!(n.map_or(V_DEFAULT, fn_closure), V_DEFAULT);
}如上所示当处理 None 的时候V_DEFAULT 作为默认值被直接返回。
map_or_else 与 map_or 类似但是它是通过一个闭包来提供默认值:
fn main() {let s Some(10);let n: Optioni8 None;let fn_closure |v: i8| v 2;let fn_default || 1;assert_eq!(s.map_or_else(fn_default, fn_closure), 12);assert_eq!(n.map_or_else(fn_default, fn_closure), 1);let o Ok(10);let e Err(5);let fn_default_for_result |v: i8| v 1; // 闭包可以对 Err 中的值进行处理并返回一个新值assert_eq!(o.map_or_else(fn_default_for_result, fn_closure), 12);assert_eq!(e.map_or_else(fn_default_for_result, fn_closure), 6);
}ok_or() and ok_or_else()
这两兄弟可以将 Option 类型转换为 Result 类型。其中 ok_or 接收一个默认的 Err 参数:
fn main() {const ERR_DEFAULT: str error message;let s Some(abcde);let n: Optionstr None;let o: Resultstr, str Ok(abcde);let e: Resultstr, str Err(ERR_DEFAULT);assert_eq!(s.ok_or(ERR_DEFAULT), o); // Some(T) - Ok(T)assert_eq!(n.ok_or(ERR_DEFAULT), e); // None - Err(default)
}而 ok_or_else 接收一个闭包作为 Err 参数:
fn main() {let s Some(abcde);let n: Optionstr None;let fn_err_message || error message;let o: Resultstr, str Ok(abcde);let e: Resultstr, str Err(error message);assert_eq!(s.ok_or_else(fn_err_message), o); // Some(T) - Ok(T)assert_eq!(n.ok_or_else(fn_err_message), e); // None - Err(default)
}自定义错误类型
虽然标准库定义了大量的错误类型但是一个严谨的项目光使用这些错误类型往往是不够的例如我们可能会为暴露给用户的错误定义相应的类型。
为了帮助我们更好的定义错误Rust 在标准库中提供了一些可复用的特征例如 std::error::Error 特征
use std::fmt::{Debug, Display};pub trait Error: Debug Display {fn source(self) - Option(Error static) { ... }
}当自定义类型实现该特征后该类型就可以作为 Err 来使用。
实际上自定义错误类型只需要实现 Debug 和 Display 特征即可source 方法是可选的而 Debug 特征往往也无需手动实现可以直接通过 derive 来派生
最简单的错误
use std::fmt;// AppError 是自定义错误类型它可以是当前包中定义的任何类型在这里为了简化我们使用了单元结构体作为例子。
// 为 AppError 自动派生 Debug 特征
#[derive(Debug)]
struct AppError;// 为 AppError 实现 std::fmt::Display 特征
impl fmt::Display for AppError {fn fmt(self, f: mut fmt::Formatter) - fmt::Result {write!(f, An Error Occurred, Please Try Again!) // user-facing output}
}// 一个示例函数用于产生 AppError 错误
fn produce_error() - Result(), AppError {Err(AppError)
}fn main(){match produce_error() {Err(e) eprintln!({}, e),_ println!(No error),}eprintln!({:?}, produce_error()); // Err({ file: src/main.rs, line: 17 })
}上面的例子很简单我们定义了一个错误类型当为它派生了 Debug 特征同时手动实现了 Display 特征后该错误类型就可以作为 Err来使用了。
事实上实现 Debug 和 Display 特征并不是作为 Err 使用的必要条件大家可以把这两个特征实现和相应使用去除然后看看代码会否报错。既然如此我们为何要为自定义类型实现这两个特征呢原因有二:
错误得打印输出后才能有实际用处而打印输出就需要实现这两个特征 可以将自定义错误转换成 Boxdyn std::error:Error 特征对象在后面的归一化不同错误类型部分. 现在再来定义一个具有错误码和信息的错误:
use std::fmt;struct AppError {code: usize,message: String,
}// 根据错误码显示不同的错误信息
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!将格式化的内容写入到指定的输出流中例如write!(std::io::stdout(), hello {}!, world)将在标准输出流中打印出hello world!。
// print!也是对输出内容进行格式化但是输出的目标是标准输出流write!(f, {}, err_msg)}
}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), // 抱歉未找到指定的页面!_ 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 }// )
}在本例中我们除了增加了错误码和消息外还手动实现了 Debug 特征原因在于我们希望能自定义 Debug 的输出内容而不是使用派生后系统提供的默认输出形式。
错误转换 From 特征
标准库、三方库、本地库各有各的精彩各也有各的错误。那么问题就来了我们该如何将其它的错误类型转换成自定义的错误类型总不能神鬼牛魔同台共舞吧。
好在 Rust 为我们提供了 std::convert::From 特征:
pub trait FromT: Sized {fn from(_: T) - Self;
}大家都使用过 String::from 函数吧它可以通过 str 来创建一个 String其实该函数就是 From 特征提供的
下面一起来看看如何为自定义类型实现 From 特征:
use std::fs::File;
use std::io;#[derive(Debug)]
struct AppError {kind: String, // 错误类型message: String, // 错误信息
}// 为 AppError 实现 std::convert::From 特征由于 From 包含在 std::prelude 中因此可以直接简化引入。
// 实现 Fromio::Error 意味着我们可以将 io::Error 错误转换成自定义的 AppError 错误
impl Fromio::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) }上面的代码中除了实现 From 外还有一点特别重要那就是 ? 可以将错误进行隐式的强制转换File::open 返回的是 std::io::Error 我们并没有进行任何显式的转换它就能自动变成 AppError 这就是 ? 的强大之处当然实现隐式转换的前提是实现了对应的From
再来看看多个不同的错误转换成 AppError 的实现
use std::fs::File;
use std::io::{self, Read};
use std::num;#[derive(Debug)]
struct AppError {kind: String,message: String,
}impl Fromio::Error for AppError {fn from(error: io::Error) - Self {AppError {kind: String::from(io),message: error.to_string(),}}
}impl Fromnum::ParseIntError for AppError {fn from(error: num::ParseIntError) - Self {AppError {kind: String::from(parse),message: error.to_string(),}}
}fn main() - Result(), AppError {let mut file File::open(hello_world.txt)?;let mut content String::new();file.read_to_string(mut content)?;let _number: usize;_number content.parse()?;Ok(())
}
// 01. 若 hello_world.txt 文件不存在
Error: AppError { kind: io, message: No such file or directory (os error 2) }// 02. 若用户没有相关的权限访问 hello_world.txt
Error: AppError { kind: io, message: Permission denied (os error 13) }// 03. 若 hello_world.txt 包含有非数字的内容例如 Hello, world!
Error: AppError { kind: parse, message: invalid digit found in string }归一化不同的错误类型
在实际项目中我们往往会为不同的错误定义不同的类型这样做非常好但是如果你要在一个函数中返回不同的错误呢例如
use std::fs::read_to_string;fn main() - Result(), std::io::Error {let html render()?;println!({}, html);Ok(())
}fn render() - ResultString, std::io::Error {let file std::env::var(MARKDOWN)?;let source read_to_string(file)?;Ok(source)
}上面的代码会报错原因在于 render 函数中的两个 ? 返回的实际上是不同的错误env::var() 返回的是 std::env::VarError而 read_to_string 返回的是 std::io::Error。
为了满足 render 函数的签名我们就需要将 env::VarError 和 io::Error 归一化为同一种错误类型。要实现这个目的有三种方式:
使用特征对象 Box dyn Error自定义错误类型使用 thiserror 下面依次来看看相关的解决方式。
Boxdyn Error
use std::fs::read_to_string;
use std::error::Error;
fn main() - Result(), Boxdyn Error {let html render()?;println!({}, html);Ok(())
}fn render() - ResultString, Boxdyn Error {let file std::env::var(MARKDOWN)?;let source read_to_string(file)?;Ok(source)
}这个方法很简单在绝大多数场景中性能也非常够用但是有一个问题Result 实际上不会限制错误的类型也就是一个类型就算不实现 Error 特征它依然可以在 ResultT, E 中作为 E 来使用此时这种特征对象的解决方案就无能为力了。
自定义错误类型
与特征对象相比自定义错误类型麻烦归麻烦但是它非常灵活因此也不具有上面的类似限制:
use std::fs::read_to_string;fn main() - Result(), MyError {let html render()?;println!({}, html);Ok(())
}fn render() - ResultString, 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 Fromstd::env::VarError for MyError {fn from(_: std::env::VarError) - Self {Self::EnvironmentVariableNotFound}
}impl Fromstd::io::Error for MyError {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()),}}
}上面代码中有一行值得注意impl std::error::Error for MyError {} 只有为自定义错误类型实现 Error 特征后才能转换成相应的特征对象。
简化错误处理
对于开发者而言错误处理是代码中打交道最多的部分之一因此选择一把趁手的武器也很重要它可以帮助我们节省大量的时间和精力好钢应该用在代码逻辑而不是冗长的错误处理上。
thiserror
thiserror可以帮助我们简化上面的第二种解决方案
use std::fs::read_to_string;fn main() - Result(), MyError {let html render()?;println!({}, html);Ok(())
}fn render() - ResultString, 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
anyhow 和 thiserror 是同一个作者开发的这里是作者关于 anyhow 和 thiserror 的原话
如果你想要设计自己的错误类型同时给调用者提供具体的信息时就使用 thiserror例如当你在开发一个三方库代码时。如果你只想要简单就使用 anyhow例如在自己的应用服务中。
use std::fs::read_to_string;use anyhow::Result;fn main() - Result() {let html render()?;println!({}, html);Ok(())
}fn render() - ResultString {let file std::env::var(MARKDOWN)?;let source read_to_string(file)?;Ok(source)
}关于如何选用 thiserror 和 anyhow 只需要遵循一个原则即可是否关注自定义错误消息关注则使用 thiserror常见业务代码否则使用 anyhow(编写第三方库代码).