1个不稳定版本
0.1.0 | 2022年1月14日 |
---|
#23 in #derived
在err-per-field中使用
9KB
188 行
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
~35K SLoC