1 个不稳定版本
| 0.1.0 | 2022 年 1 月 14 日 |
|---|
#1761 在 Rust 模式
9KB
err-per-field
此 crate 提供了对字段级错误处理的更细致控制。
当我们编写一个大型复杂项目时,我们可能将大量信息汇集到一个单独的结构体中,例如
struct GatheredInformation {
info1: Info1,
info2: Info2,
info3: Info3,
}
fn generate_info1() -> Result<Info1, Error1> { /* ... */ }
fn generate_info2() -> Option<Info2> { /* ... */ }
fn generate_info3() -> Info3 { /* ... */ }
然而,在生成这样的子信息时,我们可能会遇到 Result 或 Option。核心逻辑是,如果所有子信息都成功检索到,就处理 GatheredInformation。如果某些字段无法检索,比如说 info2,我们应该通知用户失败,并可能提供其他成功检索到的信息(如 info1 和 info3)给用户。
为了优雅地处理这个问题,动态类型语言如 JavaScript 可以相当直接
const gathered_information = gather_information();
if (gathered_information.info1 && gathered_information.info2) {
deal_with_gathered_information(gathered_information);
} else {
deal_with_error();
}
对于 Rust 这样的静态类型语言,事情变得复杂
struct GatheredInformationWrapper {
info1: Result<Info1, Error1>,
info2: Option<Info2>,
info3: Info3,
}
let gathered_information_wrapper = gather_information();
if let GatheredInformationWrapper {
info1: Ok(info1),
info2: Some(info2),
info3,
} = gathered_information_wrappper {
let gathered_information = GatheredInformation {
info1,
info2,
info3,
};
deal_with_gathered_information(gathered_information);
} else {
deal_with_error();
}
我们必须
- 定义一个
GatheredInformation的包装结构体,其字段根据生成函数是Result或Option; - 在验证检查后构建实际的
GatheredInformation; - 将实际汇集的信息传递给下游函数。
这是一个无聊的过程,额外的包装结构体非常分散注意力。
为了解决这个问题,此 crate 提供了一种相当简单和优雅的方法。
示例
use err_per_field::{ErrPerField, Wrapper};
#[derive(Debug)]
struct AnError;
#[derive(ErrPerField)]
struct Foo {
pub bar1: u8,
#[err_per_field(maybe_none)]
pub bar2: u16,
#[err_per_field(maybe_error = "AnError")]
pub bar3: u32,
pub bar4: u64,
}
fn baz1() -> u8 { 0 }
fn baz2() -> Option<u16> { None }
fn baz3() -> Result<u32, AnError> { Err(AnError) }
fn baz4() -> u64 { 0 }
fn generate_foo() -> Wrapper<Foo> {
let bar1 = baz1();
let bar2 = baz2();
let bar3 = baz3();
let bar4 = baz4();
Wrapper::<Foo> {
bar1,
bar2,
bar3,
bar4,
}
}
// If we write this in another file, we can simply import `Wrapper` type
// from `err_per_field` crate.
let result: Result<Foo, Wrapper<Foo>> = generate_foo().try_into();
assert!(result.is_err());
match result {
Ok(foo) => {
// `foo` is of type `Foo`, and you can directly use it without any worries
},
Err(foo_wrapper) => {
// `foo_wrapper` has the same fields as `foo`, but in different types,
// such as `foo_wrapper.bar2` is of type `Option<u16>` and
// `foo_wrapper.bar3` is of type `Result<u32, AnError>`
}
}
使用此 crate,我们不需要自己编写包装结构体,我们可以专注于产品的核心逻辑。我们只需要做的是
- 使用宏
ErrPerField对核心结构体Foo进行派生,并用属性标记可能为Result或Option的字段; - 在生成结构体
Foo时,使用Wrapper<Foo>作为返回类型。这个包装结构体具有与Foo相同的字段名,其类型可能是我们标记声明时的Result或Option。 - 当使用生成的结构体时,我们可以通过调用
try_into来验证其字段,如果它是有效的,则结果是Ok,内部类型是Foo,我们可以直接处理它作为核心逻辑;如果它无效,则结果是Err,内部类型是Foo的包装器,我们可以检查每个字段以进行错误处理。
用法
当我们对结构体 Foo 使用 #[derive(ErrPerField)] 时,宏生成一个包装结构体,我们可以使用 Wrapper<Foo> 来访问它。这个包装结构体具有与 Foo 相同的字段名,对于一个字段 bar,如果 foo.bar 的类型是 Bar,那么 foo_wrapper.bar 的类型是
Result<Bar, AnError>如果存在字段级属性#[err_per_field(maybe_error = "AnError")];Option<Bar>如果存在字段级属性#[err_per_field(maybe_none)];- 否则为
Bar。
Foo 将自动实现 TryFrom<Wrapper<Foo>>。如果有字段是 Err 或 None,转换失败,结果是 Err,内部值是未修改的 foo 包装器;否则转换成功,结果是 Ok,内部类型是最终 foo,所有字段都从 Result 和 Option 中提取。
依赖项
~1.5MB
~36K SLoC