#field #builder-pattern #lazy-evaluation #struct-fields #struct #builder #proc-macro

fieldx

用于构建具有延迟初始化字段、构建模式和Serde支持的结构的程序宏,重点在于声明性语法

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模式

Download history 371/week @ 2024-05-27 33/week @ 2024-06-03 7/week @ 2024-06-10 167/week @ 2024-06-17 3/week @ 2024-06-24 1/week @ 2024-07-01 119/week @ 2024-07-29 8/week @ 2024-08-05

每月 127 次下载

自定义许可

96KB
599

Rust License Crates.io Version

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_countbuild_foo 返回对应字段的初始值

在运行时,我们首先确保 order 向量是空的,这意味着没有调用任何 build_ 方法。然后我们通过其访问器方法读取 foo。然后我们确保每个 build_ 方法只被调用一次。

正如人们所注意到的那样,这里需要的手动操作量最小,因为宏处理了大部分模板代码,它还提供了一个基本的 new 关联函数。

此外,请注意,我们不需要记住字段的初始化顺序。由于它总是会初始化,所以 foo 的构建器在使用 count 时不需要担心它是否已经初始化,因为总是会初始化。

基础知识

该模块提供了两个属性:fxstructfieldx。前者负责配置结构体,后者用于调整字段参数。

该宏只能用于命名结构体,不支持联合类型或枚举。应用时,它根据提供的参数重写应用到的类型。以下是最大的更改和新增内容列表

  • 字段类型可以包装到容器类型中

    在上面的例子中,foocount 变成了 [OnceCell<String>][OnceCell] 和 OnceCell<usize>,而 order 保持不变。

  • 添加了对 Foo 的部分实现,包括支持方法和相关函数

    即,这是访问器和 new 存在的地方。

  • 根据参数,可能会添加 Default 特性的隐式实现

  • 如果需要,将实现构建器结构和相关函数 builder()

  • 此外,如果需要,还会提供用于正确 serde 支持的阴影结构体

注意 用户被强烈建议不要直接访问修改后的字段。该模块将尽最大努力通过相应的方法提供所有必要的 API。

同步和非同步结构体

如果需要一个线程安全的结构体,则 fxstruct 必须接受 sync 参数:#[fxstruct(sync, ...)]。当被指示时,宏将尽力在字段级别提供并发安全性。这意味着

  • 构建器方法保证在每个懒初始化过程中只调用一次,无论是单线程还是多线程应用程序
  • 对结构体字段的访问是锁保护的(除非用户另有要求)

同步和非同步结构体在它们与用户代码的交互方式上也非常不同。

此外,非同步结构体的非可变访问器通常返回其字段的引用。同步结构体的访问器,除非指示使用 clonecopy,或与一个非受保护的字段一起使用,返回一种锁保护器。

同步结构体字段的包装类型是非 std 的,由该模块提供。

同步结构体的受保护和未受保护的字段

对于要成为 Sync+Sentfieldx 同步结构体,所有字段都应预期为 锁保护的(或者,有时我们可以说 "保护的")。但是,“预期”并不意味着“必须”。除非使用 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] 将触发构建方法调用,并将返回的值存储在该字段中。

以下是注意事项

  1. 构建器预计是不可错误的。这一要求来源于当我们调用字段的访问器时,我们期望返回字段类型的值。由于 Rust 要求半就地处理错误(与许多其他语言中的异常相反),我们无法克服这一限制。构建器可能会引发恐慌,但这很少是一个好选择。

    对于需要可控错误处理的情况,可以为字段赋予 Result 类型。然后,obj.field()? 可以作为一种处理错误的方法。

  2. 字段构建器方法不能修改它们的对象。这种限制也源于典型的访问器方法不需要也不应该使用可变的 &self。当然,使用内部可变性总是可能的,就像这里的第一个例子。

[^only_via_method]:显然,必须通过调用相应的方法来访问。通常,它将是字段的访问器,但对于 sync 结构体,更可能是读取器。

构建器模式

重要! 首先需要指出这里存在术语上的不明确性。术语 buildbuilder 用于不同的过程,尽管在本质上相同。如前所述,惰性构建器 是返回关联字段初始值的方法。本节中的 结构体构建器 是一个收集用户初始值并能够创建原始结构体最终实例的对象。这种歧义有一些历史背景,可以追溯到 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 软件包 的相应功能

使用方法

大多数 fxstructfieldx 的参数都可以采用两种形式之一:关键字(arg),或 "函数"arg(subarg))。

此外,大多数参数都由 fxstructfieldx 共享。但它们的意义以及它们参数的解析方式可能因每个属性而略有不同。例如,如果参数接受字面字符串子参数,则它很可能是在与 fieldx 关联时作为方法名;但对于 fxstruct,它将定义方法名的公共前缀。

大多数参数之间也存在共同点:它们可以通过使用与它们一起使用的 off 子参数来暂时(例如,用于测试目的)或永久关闭。请参阅上面的例子中的 lazy(off)

属性参数

关于术语的一些说明

  • 参数 类型 确定可以接收哪些子参数

    • 关键字 - 类似于布尔型,仅接受 offkeyword(off)
    • 标志 - 与上面的关键字类似,但不接受任何参数;实际上,上面的 off 是一个 标志
    • 辅助工具 - 引入与辅助方法绑定的功能(见下文)
    • 列表函数 - 可以接受多个子参数
    • - 可以接受一些语法结构
  • 辅助方法 - 实现某些功能

    几乎所有辅助方法都是由宏生成的。唯一的例外是必须由用户提供的懒惰构建器。

  • 对于 指定参数是否特定于属性

辅助参数的子参数

辅助参数共享一些常见的子参数。我们将在下面描述它们,但如果它们的意义不明确,则最好跳过此部分,稍后再回来。

子参数 在 fxstruct 中 在 fxfield 中
off 禁用辅助工具 禁用辅助工具
一个非空字符串字面量("foo" 方法名前缀 显式方法名(不使用前缀)
attributes_fn 对应类型的辅助方法的默认属性 字段的辅助方法的属性
publicpublic(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")]

当前支持以下族成员:attributesattributes_fnattributes_impl。特定上下文中支持哪些已在下面进行文档说明。

fxstruct 参数

attributes

类型list

builderserde 参数生成的结构体提供默认属性。

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 方法或类型的构建器产生)将被包装成引用计数指针 RcArc,具体取决于类型的 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());

readerwriter

类型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_mutreader/writer之间差异的部分

类型: 标志

默认情况下强制所有字段使用锁包装。可以使用lock(off)显式禁用。与reader/writer参数相同,但不会安装任何方法。

clearerpredicate

类型helper

这两个选项在意义上紧密耦合,尽管可以单独使用。

谓词辅助方法返回bool,用于确定字段是否已设置。它们是通用的,因为无论结构体是sync还是非sync,字段是延迟加载的还是仅仅是可选的,您始终使用相同的方法。

清除器辅助方法用于将字段重置为未初始化状态。对于可选字段,它将简单地意味着它将包含None。延迟加载的字段将在下一次读取时重新初始化。

清除器返回当前字段值。如果字段已未初始化(或尚未初始化)None将被返回。

使用这两个中的任何一个都会自动使字段可选,除非是延迟加载的。

查看示例,在可选字段部分。

可选

类型keyword

明确使所有字段可选。当不需要谓词或清除器辅助程序时很有用。

public(...)private

指定辅助程序的默认值。有关更多详细信息,请参阅上面的子参数部分

clonecopy

指定访问器辅助程序的默认值。

serde

类型: 函数

通过默认关闭的serde功能启用。

将在下面的部分中更详细地讨论序列化和反序列化支持。目前重要的是要知道,由于使用容器类型,结构体的直接序列化几乎是不可能的。因此,fieldx通过创建一个特殊的阴影结构体来利用serdeintofrom。默认情况下,阴影结构体的名称是原始名称,前面加上两个下划线,后面加上后缀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 的参数

此时,值得您回顾一下 辅助程序的子参数 以及它们在 fxstructfieldx 属性之间的语义差异。

跳过

类型: 标志

保持此字段不变。当跳过时,fieldx 所尊重的唯一参数是 default

lazy

类型helper

将字段标记为延迟加载。

重命名

类型: 函数

指定字段的替代名称。该替代名称将用于形成方法名称,并且当启用 serde 功能时,用于序列化名称[^unless_in_serde]。

[^unless_in_serde]: 除非使用 serde 参数指定不同的替代名称用于序列化。

getget_mutsetreaderwriterclearerpredicateoptional

类型helper

其语法和语义与相应的 fxstruct 参数类似

可选

类型keyword

即使不需要 predicateclearer,也要明确地将字段标记为可选。

public(...)private

辅助方法的字段默认可见性。有关更多详细信息,请参阅上方的子参数部分

serde

类型: 函数

在字段级别,此选项的作用方式与结构体级别大致相同。但也有一些不同之处

  • 字符串字面量子参数绕过到 serde 中的 字段级别 rename
  • default 负责字段的默认值;与结构体级别不同,它不使用 Into 特性
  • attributes 将应用于字段本身
  • serialize/deserialize 控制字段的序列化和反序列化

into

类型keyword

setbuilder 参数设置默认值。

builder

类型: 函数

结构体级别的 builder 大致相同。字段的具体情况是

  • 没有 attributes_impl(消耗,但忽略)
  • 字符串字面量指定了此字段的构建器类型的设置器方法名
  • attributesattributes_fn 分别应用于构建器字段和构建器设置器方法

我们是否需要 Default 特性?

除非使用 fxstruct 属性显式使用 default 参数,否则 fieldx 尝试避免实现 Default 特性,除非确实需要。以下是确定是否需要实现的条件

  1. 方法 new 由过程宏生成。

    这实际上是默认行为,可以通过 fxstruct 属性的 no_new 参数禁用。

  2. 字段被赋予了一个 default 值。

  3. 结构体是 sync 且具有懒加载字段。

为什么 Sync 结构体需要 get/get_mutreader/writer

一开始可能会觉得为什么 Sync 结构体有基本两种不同的访问器,但这是有原因的。

首先,让我们考虑以下重要因素

  • 受保护的字段不能直接提供其值;需要锁保护来实现这一点
  • 预期的懒加载字段在读取时始终会得到某个值

让我们专注于懒加载字段的情况。它们具有所有锁保护字段和可选字段的属性,因此我们在 get/get_mutreader/writer 的区别中没有任何损失。

getreader

一个裸骨的 get 访问器辅助器与 reader 辅助器相同[^get_reader_guts]。但是,一旦用户决定他们想要 copyclone 访问器行为,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_mutwriter

此案例与之前显著不同。尽管两个助手都负责更改字段,但 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(也通过 clearerproxy 激活) Option<T> FXRwLock<Option<T>>
lockreader 和/或 writer N/A FXRwLock<T>

显然,跳过的字段保留其原始类型。当然,如果此类字段为非 Send 或非 Sync 类型,则尽管宏 fxstruct 做了所有努力,整个结构体也将缺少这些特性。

此外,在实现延迟字段的初始化方面也存在差异。非同步结构体直接在其访问器方法中执行。同步结构体将此功能委托给 FXProxy 类型。

特质

fieldx 还为相应类型的结构体实现了特质 FXStructNonSyncFXStructSync。这两个特质都是空的,仅用于区分结构体与 fieldx 的非结构体以及彼此。对于两者,FXStruct 是超特质。

同步原语

sync 结构体的功能由 parking_lot 包提供的原语支持。

通过 serde 支持反序列化和序列化

透明地反序列化容器类型是一项复杂的任务。幸运的是,serde 允许我们使用特殊参数 frominto 通过影子结构体进行间接序列化。这种方式由 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