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