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