4个版本
| 0.1.3 | 2024年8月2日 |
|---|---|
| 0.1.2 | 2024年6月19日 |
| 0.1.1 | 2024年6月2日 |
| 0.1.0 | 2024年5月29日 |
#574 in Rust模式
每月 127 次下载
96KB
599 行
fieldx v0.1.3
用于构建具有延迟初始化字段、构建模式和serde支持的结构的程序宏,重点在于声明性语法。
让我们从一个例子开始
use fieldx::fxstruct;
#[fxstruct( lazy )]
struct Foo {
count: usize,
foo: String,
#[fieldx( lazy(off), get )]
order: RefCell<Vec<&'static str>>,
}
impl Foo {
fn build_count(&self) -> usize {
self.order.borrow_mut().push("Building count.");
12
}
fn build_foo(&self) -> String {
self.order.borrow_mut().push("Building foo.");
format!("foo is using count: {}", self.count())
}
}
let foo = Foo::new();
assert_eq!(foo.order().borrow().len(), 0);
assert_eq!(foo.foo(), "foo is using count: 12");
assert_eq!(foo.foo(), "foo is using count: 12");
assert_eq!(foo.order().borrow().len(), 2);
assert_eq!(foo.order().borrow()[0], "Building foo.");
assert_eq!(foo.order().borrow()[1], "Building count.");
这里发生的事情是
- 一个所有字段默认为
lazy的结构体 - 对于字段
order,显式禁用延迟初始化 - 方法
build_count和build_foo返回对应字段的初始值
在运行时,我们首先确保 order 向量是空的,这意味着没有调用任何 build_ 方法。然后我们通过其访问器方法读取 foo。然后我们确保每个 build_ 方法只被调用一次。
正如人们所注意到的那样,这里需要的手动操作量最小,因为宏处理了大部分模板代码,它还提供了一个基本的 new 关联函数。
此外,请注意,我们不需要记住字段的初始化顺序。由于它总是会初始化,所以 foo 的构建器在使用 count 时不需要担心它是否已经初始化,因为总是会初始化。
基础知识
该模块提供了两个属性:fxstruct 和 fieldx。前者负责配置结构体,后者用于调整字段参数。
该宏只能用于命名结构体,不支持联合类型或枚举。应用时,它根据提供的参数重写应用到的类型。以下是最大的更改和新增内容列表
-
字段类型可以包装到容器类型中
在上面的例子中,
foo和count变成了 [OnceCell<String>][OnceCell] 和OnceCell<usize>,而order保持不变。 -
添加了对
Foo的部分实现,包括支持方法和相关函数即,这是访问器和
new存在的地方。 -
根据参数,可能会添加
Default特性的隐式实现 -
如果需要,将实现构建器结构和相关函数
builder() -
此外,如果需要,还会提供用于正确
serde支持的阴影结构体
注意 用户被强烈建议不要直接访问修改后的字段。该模块将尽最大努力通过相应的方法提供所有必要的 API。
同步和非同步结构体
如果需要一个线程安全的结构体,则 fxstruct 必须接受 sync 参数:#[fxstruct(sync, ...)]。当被指示时,宏将尽力在字段级别提供并发安全性。这意味着
- 构建器方法保证在每个懒初始化过程中只调用一次,无论是单线程还是多线程应用程序
- 对结构体字段的访问是锁保护的(除非用户另有要求)
同步和非同步结构体在它们与用户代码的交互方式上也非常不同。
此外,非同步结构体的非可变访问器通常返回其字段的引用。同步结构体的访问器,除非指示使用 clone 或 copy,或与一个非受保护的字段一起使用,返回一种锁保护器。
同步结构体字段的包装类型是非 std 的,由该模块提供。
同步结构体的受保护和未受保护的字段
对于要成为 Sync+Sent 的 fieldx 同步结构体,所有字段都应预期为 锁保护的(或者,有时我们可以说 "保护的")。但是,“预期”并不意味着“必须”。除非使用 fxstruct 属性(即使用 结构级 参数)指定的默认值告诉否则,未使用 fieldx 属性带有相应参数的字段将保持 未受保护的。即
#[fxstruct(sync)]
struct Foo {
#[fieldx(lazy)]
foo: String, // protected
#[fieldx(get_mut)]
bar: String, // unprotected
}
当然,结构体是否保持线程安全将取决于未受保护字段的安全性。
可选字段
在这个上下文中,“可选”与 Option 类型的意义相同。当然,可以简单地使用核心类型声明一个字段(实际上,fieldx 内部就是这样做的)。那么使用 fieldx 的优点是什么呢?
首先,手动声明可能意味着额外的样板代码来实现访问器,以及其他事情。使用 fieldx 的大部分内容可以隐藏在单个声明之下
#[fxstruct]
struct Foo {
#[fieldx(predicate, clearer, get, set(into))]
description: String,
}
let mut obj = Foo::new();
assert!( !obj.has_description() );
obj.set_description("foo");
assert!( obj.has_description() );
assert_eq!( obj.description(), &Some(String::from("foo")) );
obj.clear_description();
assert!( !obj.has_description() );
<digression_mode> 此外,从美学角度来看,某些 has_description 比以下代码更具吸引力:obj.description().is_some()。</digression_mode>
接下来,sync 结构体的可选字段默认情况下由锁保护。这可以通过显式调用 lock(off) 来改变,但必须意识到结构体的同步状态将取决于字段的健壮性。
最后一点要注意的是,如果将字段转换为 lazy 有助于某些情况,那么重构可以简化为仅添加相应的 fieldx 属性并为其实现一个新的构建器。
惰性协议
尽管这是一个非常简单的概念,但惰性有自己的特性。如上所示,基本原理是这样的:当我们将字段声明为 lazy 时,宏将其包装成某种代理容器类型(对于非同步结构体是 OnceCell)。从未初始化的字段中进行的第一次读取[^only_via_method] 将触发构建方法调用,并将返回的值存储在该字段中。
以下是注意事项
-
构建器预计是不可错误的。这一要求来源于当我们调用字段的访问器时,我们期望返回字段类型的值。由于 Rust 要求半就地处理错误(与许多其他语言中的异常相反),我们无法克服这一限制。构建器可能会引发恐慌,但这很少是一个好选择。
对于需要可控错误处理的情况,可以为字段赋予
Result类型。然后,obj.field()?可以作为一种处理错误的方法。 -
字段构建器方法不能修改它们的对象。这种限制也源于典型的访问器方法不需要也不应该使用可变的
&self。当然,使用内部可变性总是可能的,就像这里的第一个例子。
[^only_via_method]:显然,必须通过调用相应的方法来访问。通常,它将是字段的访问器,但对于 sync 结构体,更可能是读取器。
构建器模式
重要! 首先需要指出这里存在术语上的不明确性。术语 build 和 builder 用于不同的过程,尽管在本质上相同。如前所述,惰性构建器 是返回关联字段初始值的方法。本节中的 结构体构建器 是一个收集用户初始值并能够创建原始结构体最终实例的对象。这种歧义有一些历史背景,可以追溯到 Perl 的 Moo 模块成为作者的 主要工具的时代。然后它被 Raku AttrX::Mooish 采用,最后自动进入 fieldx,最初没有实现构建器模式。
由 fxstruct 宏生成的默认 new 方法不接受任何参数,仅创建一个由类型默认值初始化的裸骨对象。对于结构体字段的提交自定义值,最好通过使用构建器模式来完成。
#[fxstruct(builder)]
struct Foo {
#[fieldx(lazy)]
description: String,
count: usize,
}
impl Foo {
fn build_description(&self) -> String {
format!("this is item #{}", self.count)
}
}
let obj = Foo::builder()
.count(42)
.build()
.expect("Foo builder failure");
assert_eq!( obj.description(), &String::from("this is item #42") );
let obj = Foo::builder()
.count(13)
.description(String::from("count is ignored"))
.build()
.expect("Foo builder failure");
// Since the `description` is given a value the `count` field is not used
assert_eq!( obj.description(), &String::from("count is ignored") );
由于在构建新对象实例时可能发生的唯一与 fieldx 相关的失败情况是未提供值的必填字段,如果发生这种情况,build() 方法将返回 FieldXError。
软件包功能
以下功能由该软件包支持
| 功能 | 描述 |
|---|---|
diagnostics |
启用编译时错误的额外诊断。需要 Rust 夜间工具集。 |
serde |
启用对 serde 序列化的支持。 |
send_guard |
请参阅 parking_lot 软件包 的相应功能 |
使用方法
大多数 fxstruct 和 fieldx 的参数都可以采用两种形式之一:关键字(arg),或 "函数"(arg(subarg))。
此外,大多数参数都由 fxstruct 和 fieldx 共享。但它们的意义以及它们参数的解析方式可能因每个属性而略有不同。例如,如果参数接受字面字符串子参数,则它很可能是在与 fieldx 关联时作为方法名;但对于 fxstruct,它将定义方法名的公共前缀。
大多数参数之间也存在共同点:它们可以通过使用与它们一起使用的 off 子参数来暂时(例如,用于测试目的)或永久关闭。请参阅上面的例子中的 lazy(off)。
属性参数
关于术语的一些说明
-
参数 类型 确定可以接收哪些子参数
- 关键字 - 类似于布尔型,仅接受
off:keyword(off) - 标志 - 与上面的关键字类似,但不接受任何参数;实际上,上面的
off是一个 标志 - 辅助工具 - 引入与辅助方法绑定的功能(见下文)
- 列表 或 函数 - 可以接受多个子参数
- 元 - 可以接受一些语法结构
- 关键字 - 类似于布尔型,仅接受
-
辅助方法 - 实现某些功能
几乎所有辅助方法都是由宏生成的。唯一的例外是必须由用户提供的懒惰构建器。
-
对于 指定参数是否特定于属性
辅助参数的子参数
辅助参数共享一些常见的子参数。我们将在下面描述它们,但如果它们的意义不明确,则最好跳过此部分,稍后再回来。
| 子参数 | 在 fxstruct 中 | 在 fxfield 中 |
|---|---|---|
off |
禁用辅助工具 | 禁用辅助工具 |
一个非空字符串字面量("foo") |
方法名前缀 | 显式方法名(不使用前缀) |
attributes_fn |
对应类型的辅助方法的默认属性 | 字段的辅助方法的属性 |
public、public(crate)、public(super)、public(some::module)、private |
默认可见性 | 字段辅助器的可见性 |
例如
#[fxstruct( get( "get_", public(crate) ) )]
将生成以 get_ 为前缀的访问器方法,其可见性为 pub(crate)
let foo = obj.get_foo();
使用
#[fieldx( get( "special_type", private ) )]
ty: String,
拥有字段的结构的某个方法可以使用访问器如下
let foo = self.special_type();
attributes* 子参数族
有时可能需要为各种生成的语法元素指定属性,例如方法或辅助结构。当适用时,此功能由 attributes*(子)参数支持。其语法为 attributes(<attr1>, <attr2>, ...),其中指定了精确的 <attr>,正如在代码中指定一样,但省略了起始的 #[ 和结束的 ]。
例如,attributes_fn(allow(dead_code), cfg(feature = "myfeature")) 将扩展成类似的内容
#[allow(dead_code)]
#[cfg(feature = "myfeature")]
当前支持以下族成员:attributes、attributes_fn 和 attributes_impl。特定上下文中支持哪些已在下面进行文档说明。
fxstruct 参数
attributes
类型:list
为 builder 和 serde 参数生成的结构体提供默认属性。
attributes_impl
类型:list
应用于结构体实现的属性。
sync
类型:keyword
声明结构体为线程安全。
lazy
类型:helper
为除标记为 lazy(off) 的字段外的所有字段启用懒模式。
builder
类型:helper
通过引入关联函数和构建器类型启用构建器功能。
#[fxstruct(builder, get)]
struct Foo {
description: String,
}
let obj = Foo::builder()
.description(String::from("some description"))
.build()?;
assert_eq!(obj.description(), "some description");
builder 的字面字符串子参数定义了构建器方法设置器的前缀。例如,使用 builder("set_"),将使用 .set_description(...) 调用。
其他子参数
-
attributes(见上面的部分)– 构建器结构体的属性 -
attributes_impl- 结构体实现的属性 -
into– 强制所有构建器设置方法尝试使用.into()方法进行自动类型转换使用
into,上面的示例不需要String::from,调用可以像这样:.description("some description")
rc
类型:keyword
使用此参数,类型的新实例(由 new 方法或类型的构建器产生)将被包装成引用计数指针 Rc 或 Arc,具体取决于类型的 sync 状态。
no_new
类型:keyword
禁用生成 new 方法。这在用户想要自己的 new 方法时很有用。
使用此选项,宏可能避免为结构体生成 Default 实现。更多详情请见下面的部分。
default
类型:keyword
强制为结构体生成 Default 实现。
get
类型:helper
启用或禁用所有字段的获取器方法,除非字段有其他标记。
除了标准辅助参数外,还可以将访问器配置为
clone- 克隆,即返回字段值的克隆(必须实现Clone)copy- 复制,即返回字段值的副本(必须实现Copy)as_ref– 仅适用于字段值为可选的情况;它使得访问器返回一个Option<&T>而不是&Option<T>
get_mut
类型:helper
请求一个可变访问器。由于 get 的附加选项都不适用于此处[^no_copy_for_mut],因此只接受基本 辅助子参数。
可变访问器与不可变访问器具有相同的名称,但带有 _mut 后缀,除非用户明确给出名称。
#[fxstruct(get, get_mut)]
struct Foo {
description: String,
}
let mut obj = Foo::new();
*obj.description_mut() = "some description".to_string();
assert_eq!(obj.description(), "some description");
[^no_copy_for_mut]: 如果你已经拥有它,那么可变副本有什么意义?
set
类型:helper
请求设置方法。如果提供了字面字符串子参数,则将其用作设置方法的前缀,而不是默认的 set_。
需要额外的子参数
into:使用Into特性自动将值转换为字段类型
#[fxstruct(set(into), get)]
struct Foo {
description: String,
}
let mut obj = Foo::new();
obj.set_description("some description");
assert_eq!(obj.description(), &"some description".to_string());
reader,writer
类型:helper
仅对sync结构体有意义。请求返回只读或读写锁保护器的读取器和写入器方法。
类似于设置器,方法名使用read_和write_前缀形成,相应地,添加到字段名前。
#[fxstruct(sync, reader, writer)]
struct Foo {
description: String,
}
let obj = Foo::new();
{
let mut wguard = obj.write_description();
*wguard = String::from("let's use something different");
}
{
let rguard = obj.read_description();
assert_eq!(*rguard, "let's use something different".to_string());
}
参见关于get/get_mut和reader/writer之间差异的部分
锁
类型: 标志
默认情况下强制所有字段使用锁包装。可以使用lock(off)显式禁用。与reader/writer参数相同,但不会安装任何方法。
clearer和predicate
类型:helper
这两个选项在意义上紧密耦合,尽管可以单独使用。
谓词辅助方法返回bool,用于确定字段是否已设置。它们是通用的,因为无论结构体是sync还是非sync,字段是延迟加载的还是仅仅是可选的,您始终使用相同的方法。
清除器辅助方法用于将字段重置为未初始化状态。对于可选字段,它将简单地意味着它将包含None。延迟加载的字段将在下一次读取时重新初始化。
清除器返回当前字段值。如果字段已未初始化(或尚未初始化)None将被返回。
使用这两个中的任何一个都会自动使字段可选,除非是延迟加载的。
可选
类型:keyword
明确使所有字段可选。当不需要谓词或清除器辅助程序时很有用。
public(...)、private
指定辅助程序的默认值。有关更多详细信息,请参阅上面的子参数部分。
clone、copy
指定访问器辅助程序的默认值。
serde
类型: 函数
通过默认关闭的serde功能启用。
将在下面的部分中更详细地讨论序列化和反序列化支持。目前重要的是要知道,由于使用容器类型,结构体的直接序列化几乎是不可能的。因此,fieldx通过创建一个特殊的阴影结构体来利用serde的into和from。默认情况下,阴影结构体的名称是原始名称,前面加上两个下划线,后面加上后缀Shadow:__FooShadow。
以下子参数受支持
- 使用字符串字面值来为阴影结构体指定用户定义的名称
off完全禁用序列化和反序列化支持attributes(...)- 应用到阴影结构的自定义属性public(...),private– 指定阴影结构体的可见性serialize- 启用或禁用(serialize(off))结构体的序列化支持deserialize- 启用或禁用(deserialize(off))结构体的反序列化支持default- 当有缺失字段时,是否必须使用serde的默认值,以及可能从哪里获取这些默认值forward_attrs- 要转发到阴影结构体相应字段的字段属性列表
关于 default 的说明
子参数的有效参数
- 与 容器级别的
serde属性default相同意义的字符串字面量 - 到绑定到我们类型实例的符号的路径:
my_crate::FOO_DEFAULT - 将被字面量使用的调用式路径:
Self::serde_default()
最后一个选项更可取,因为 fieldx 将解析它,并将任何找到的 Self 引用替换为实际的结构体名称,使得未来的重命名更容易。
关于 default 的工作方式有一个可能有用的“技巧”。内部,子参数返回的任何类型都会通过具有特质 Into 的特质转换为阴影类型。这允许您使用原始结构体,因为为其自动生成了特质实现。请参阅以下测试示例
#[cfg(feature = "serde")]
#[fxstruct(sync, get, serde("BazDup", default(Self::serde_default())))]
#[derive(Clone)]
pub(super) struct Baz {
#[fieldx(reader)]
f1: String,
f2: String,
}
impl Baz {
fn serde_default() -> Fubar {
Fubar {
postfix: "from fubar".into()
}
}
}
struct Fubar {
postfix: String,
}
impl From<Fubar> for BazDup {
fn from(value: Fubar) -> Self {
Self {
f1: format!("f1 {}", value.postfix),
f2: format!("f2 {}", value.postfix),
}
}
}
let json_src = r#"{"f1": "f1 json"}"#;
let foo_de = serde_json::from_str::<Baz>(&json_src).expect("Bar deserialization failure");
assert_eq!(*foo_de.f1(), "f1 json".to_string());
assert_eq!(*foo_de.f2(), "f2 from fubar".to_string());
fieldx 的参数
此时,值得您回顾一下 辅助程序的子参数 以及它们在 fxstruct 和 fieldx 属性之间的语义差异。
跳过
类型: 标志
保持此字段不变。当跳过时,fieldx 所尊重的唯一参数是 default。
lazy
类型:helper
将字段标记为延迟加载。
重命名
类型: 函数
指定字段的替代名称。该替代名称将用于形成方法名称,并且当启用 serde 功能时,用于序列化名称[^unless_in_serde]。
[^unless_in_serde]: 除非使用 serde 参数指定不同的替代名称用于序列化。
get、get_mut、set、reader、writer、clearer、predicate、optional
类型:helper
其语法和语义与相应的 fxstruct 参数类似
可选
类型:keyword
即使不需要 predicate 或 clearer,也要明确地将字段标记为可选。
public(...)、private
辅助方法的字段默认可见性。有关更多详细信息,请参阅上方的子参数部分。
serde
类型: 函数
在字段级别,此选项的作用方式与结构体级别大致相同。但也有一些不同之处
- 字符串字面量子参数绕过到
serde中的 字段级别rename default负责字段的默认值;与结构体级别不同,它不使用Into特性attributes将应用于字段本身serialize/deserialize控制字段的序列化和反序列化
into
类型:keyword
为 set 和 builder 参数设置默认值。
builder
类型: 函数
与结构体级别的 builder 大致相同。字段的具体情况是
- 没有
attributes_impl(消耗,但忽略) - 字符串字面量指定了此字段的构建器类型的设置器方法名
attributes和attributes_fn分别应用于构建器字段和构建器设置器方法
我们是否需要 Default 特性?
除非使用 fxstruct 属性显式使用 default 参数,否则 fieldx 尝试避免实现 Default 特性,除非确实需要。以下是确定是否需要实现的条件
-
方法
new由过程宏生成。这实际上是默认行为,可以通过
fxstruct属性的no_new参数禁用。 -
字段被赋予了一个
default值。 -
结构体是
sync且具有懒加载字段。
为什么 Sync 结构体需要 get/get_mut 和 reader/writer?
一开始可能会觉得为什么 Sync 结构体有基本两种不同的访问器,但这是有原因的。
首先,让我们考虑以下重要因素
- 受保护的字段不能直接提供其值;需要锁保护来实现这一点
- 预期的懒加载字段在读取时始终会得到某个值
让我们专注于懒加载字段的情况。它们具有所有锁保护字段和可选字段的属性,因此我们在 get/get_mut 和 reader/writer 的区别中没有任何损失。
get 与 reader
一个裸骨的 get 访问器辅助器与 reader 辅助器相同[^get_reader_guts]。但是,一旦用户决定他们想要 copy 或 clone 访问器行为,reader 就成为访问字段锁保护器的唯一方式
[^get_reader_guts]: 事实上,它们甚至使用相同的方法生成代码。
#[fxstruct(sync)]
struct Foo {
#[fieldx(get(copy), reader, lazy)]
bar: u32
}
impl Foo {
fn build_bar(&self) -> u32 { 1234 }
fn do_something(&self) -> u32 {
// We need to protect the field value until we're done using it.
let bar_guard = self.read_bar();
let outcome = *bar_guard * 2;
outcome
}
}
let foo = Foo::new();
assert_eq!(foo.do_something(), 2468);
get_mut 与 writer
此案例与之前显著不同。尽管两个助手都负责更改字段,但 get_mut 助手仍然首先是访问者,而 writer 则不是。在延迟字段的上下文中,这意味着 get_mut 保证字段首先被初始化。然后我们可以更改其值。
相反,writer 提供了对字段容器的直接和即时访问。它允许在不需要构建方法的情况下将值存储在其中。由于构建延迟字段可能很昂贵,在实际上不需要它的情况下,避免构建可能是有帮助的[^sync_writer_vs_builder]。
[^sync_writer_vs_builder]:有时,如果值在创建结构体实例之前已知,那么使用构建器而不是写入器可能是有意义的。
基本上,由 writer 助手返回的守卫只能做两件事:将整个值存储到字段中,并清除字段。
#[fxstruct(sync)]
struct Foo {
#[fieldx(get_mut, get(copy), writer, lazy)]
bar: u32
}
impl Foo {
fn build_bar(&self) -> u32 {
eprintln!("Building bar");
1234
}
fn do_something1(&self) {
eprintln!("Using writer.");
let mut bar_guard = self.write_bar();
bar_guard.store(42);
}
fn do_something2(&self) {
eprintln!("Using get_mut.");
let mut bar_guard = self.bar_mut();
*bar_guard = 12;
}
}
let foo = Foo::new();
foo.do_something1();
assert_eq!(foo.bar(), 42);
let foo = Foo::new();
foo.do_something2();
assert_eq!(foo.bar(), 12);
此示例预期输出如下
Using writer.
Using get_mut.
Building bar
如您所见,使用 bar_mut 访问器会导致调用 build_bar 方法。
内部工作原理
正如在基础知识部分中提到的,fieldx 使用 fxstruct 应用重写结构。以下表格揭示了字段的最终类型。T 表格中的表示由用户指定的原始字段类型;O 是原始结构体类型。
| 字段参数 | 非同步类型 | 同步类型 |
|---|---|---|
lazy |
OnceCell<T> |
[FXProxy<O, T>] |
optional(也通过 clearer 和 proxy 激活) |
Option<T> |
FXRwLock<Option<T>> |
lock、reader 和/或 writer |
N/A | FXRwLock<T> |
显然,跳过的字段保留其原始类型。当然,如果此类字段为非 Send 或非 Sync 类型,则尽管宏 fxstruct 做了所有努力,整个结构体也将缺少这些特性。
此外,在实现延迟字段的初始化方面也存在差异。非同步结构体直接在其访问器方法中执行。同步结构体将此功能委托给 FXProxy 类型。
特质
fieldx 还为相应类型的结构体实现了特质 FXStructNonSync 和 FXStructSync。这两个特质都是空的,仅用于区分结构体与 fieldx 的非结构体以及彼此。对于两者,FXStruct 是超特质。
同步原语
sync 结构体的功能由 parking_lot 包提供的原语支持。
通过 serde 支持反序列化和序列化
透明地反序列化容器类型是一项复杂的任务。幸运的是,serde 允许我们使用特殊参数 from 和 into 通过影子结构体进行间接序列化。这种方式由 serde 实现(这也是一个很好的原因),要求我们的原始结构体实现 Clone 特性。 fxstruct 不自动添加 #[derive(Clone)],因为实现该特性可能需要用户手动工作。
通常情况下,不需要干预序列化过程。但是,如果出现这种需要,以下实现细节可能会有所帮助。
- 影子结构体镜像懒和可选原始字段的
Option包装。 - 可以使用字符串字面量子参数为结构体指定自定义名称,该参数用于 serde 参数。
- 如果它们在
serde参数的forward_attrs子参数中列出,则影子字段可以与原始字段共享其属性。 forward_attrs总是应用于字段,无论是否与结构体级或字段级serde参数一起使用。- 如果您需要将自定义属性应用于影子结构体,请使用
serde子参数的attributes*系列函数。 - 对于非共享字段级自定义属性也是如此:它们应使用
serde字段级attributes*声明。
许可证
在 BSD 3-Clause 许可证 下授权。
依赖项
~2.1–7.5MB
~59K SLoC