9 个版本
0.4.1 | 2022 年 9 月 21 日 |
---|---|
0.4.0 | 2022 年 9 月 9 日 |
0.3.1 | 2021 年 2 月 15 日 |
0.2.2 | 2021 年 2 月 10 日 |
0.1.1 | 2020 年 11 月 17 日 |
#1945 in Rust 模式
用于 2 crates
10KB
54 行
Variant Access
一组特质和宏,用于定义基于 C++ 标准库中的 std::variant API 的 Rust 枚举的通用 API
基本用法
考虑以下枚举
enum Enum {
F1(i32),
F2(bool)
}
我们当然可以直接使用这样的枚举,使用字段名和 match 语句。此 crate 中提供的特质允许在不显式使用标签的情况下执行相同的内部值访问。
这模仿了 C++ 类型 std::variant 的 API,它类似于 Rust 枚举,但没有为每个可能的激活字段显式命名。考虑以下示例
let instance = Enum::F1(42);
if instance.has_variant::<i32>() && instance.contains_variant::<i32>().unwrap() {
let inner: &i32 = instance.get_variant().unwrap();
...
}
上面的代码首先检查实例具有类型 i32
的字段,然后检查这是激活字段,然后获取其中包含的原始值的引用。
一般来说,此 crate 中提供的特质为枚举提供了以下功能
let mut instance = ...;
// determines whether on of the possible fields has type T
let result: bool = instance.has_variant::<T>();
// determines whether or not the active field has type T.
// If not field has type T, returns Err
let result: Result<bool, ..> = instance.contains_variant::<T>();
// retrieves a reference to the raw value of the field of type T. If the
// active field is not of type T, get_variant returns Err causing the following
// code to panic. If no field has type T, the following will not compile.
let inner: &T = instance.get_variant().unwrap();
// retrieves a mutable reference to the raw value of the field of type T. If the
// active field is not of type T, get_variant returns Err causing the following
// code to panic. If no field has type T, the following will not compile.
let inner: &mut T = instance.get_variant_mut().unwrap();
// If instance has a field of type bool, this becomes the active field with
// value of `false`. Otherwise, this will not compile.
instance.set_variant(false);
// Since instance can have multiple number-like fields, the following can be
// used to enforce that the field of type i64 is set (if it exists).
// Otherwise the outcome will be ambiguous to the user.
instance.set_variant(3 as i64);
对于枚举类型(受下文类型要求部分中详细说明的限制),可以使用 derive_variant_access
宏推导这些特质。此宏推导出此 crate 中所有特质。
use variant_access_derive::*;
#[derive(VariantAccess)]
enum Enum {
F1(i32),
F2(bool)
}
为了使此宏成功,必须应用一些限制。首先,它只能应用于枚举。其次,每个字段都必须具有唯一类型。如果枚举本身有任何字段具有多个字段或任何命名字段,则宏将不会工作(这可能在将来得到扩展)。如果不符合这些条件之一,则代码将无法编译。
动机
默认情况下,访问 Rust 枚举中的激活字段需要直接使用用于激活字段的标签。在需要类似变体/联合类型更统一接口的情况下,这很成问题。一个主要的例子是计算机生成的代码。
对于自动生成的类型,支持联合类型很困难,因为需要生成一个枚举,其名称和字段标签也将被自动生成,并且对于任何用户来说都是不可见的。因此,不需要知道字段名即可使用的统一接口允许使用此类自动生成的类型,而不会给最终用户带来过多的负担。
以下是一个例子:由 protobuf 模式生成的代码默认将所有内部值设为私有,并提供获取器和设置器以统一对这些实体的交互。
支持的功能
派生宏能够完全区分类型,即使它们具有相同的名称但位于不同的模块中。通过使用 std::any::TypeId
实现了完整的命名空间解析。例如
#[derive(Debug, PartialEq)]
pub struct Complex {
field_one: bool,
field_two: f64
}
pub mod namespace {
#[derive(Debug, PartialEq)]
pub struct Complex {
pub field_one: bool,
pub field_two: f64
}
#[derive(VariantAccess, PartialEq, Debug)]
pub enum ComplexEnum {
F1(Complex),
F2(super::Complex)
}
}
此代码有效,并且各种特质方法可以区分类型 Complex
和 namespace::Complex
。
泛型也受到支持。例如
#[derive(PartialEq, Debug)]
pub struct Test<T, U>{
inner: T,
outer: U,
}
#[derive(VariantAccess, PartialEq, Debug)]
pub enum Enum<Y: 'static, X: 'static> {
F1(Y),
F2(Test<X, Y>)
}
此代码有效,这意味着实例化后的特质方法将自动工作,例如,对于类型 Enum<i64, bool>
。例如,
fn main() {
let test = Enum::<i64, bool>::F2(Test{inner: true, outer: 2});
let value: &Test<bool, i64> = test.get_variant().unwrap();
assert_eq!(value, Test{inner: true, outer: 2});
}
为了支持具有多个泛型参数的枚举定义,必须使用标记结构体以避免冲突定义,请参阅 Stackoverflow 上的这个问题 问题。
因此,在上面的例子中,还将创建以下模块和标记结构体
#[allow(non_snake_case)]
mod variant_access_Enum {
pub (crate) struct F1;
pub (crate) struct F2;
}
所以,如果你曾想过自己创建模块 variant_access_Enum
,请注意!😝
我们还提供了一个特质和函数,用于根据特定类型的值创建变体的实例。考虑以下示例
use variant_access_traits::*;
use variant_access_derive::*;
#[(VariantAccessDerive)]
enum HorribleComputerGeneratedEnumName {
AwfulComputerGeneratedField1(f64),
AwfulComputerGeneratedField2(bool)
}
struct LovelyStruct {
lovely_field_name: HorribleComputerGeneratedEnumName
}
fn main() {
let lovely = LovelyStruct{lovely_field_name: create_variant_from(3.0)};
}
create_variant_from
函数能够推断出,由于 lovely_field_name
的类型是 HorribleComputerGeneratedEnumName
,而函数的输入是 f64
,因此它应该返回 HorribleComputerGeneratedEnumName::AwfulComputerGeneratedField1(3.0)
。此示例回到了这个 crate 的原始动机。
类型要求
为了使特质和/或派生宏能够正常工作,您的枚举定义必须满足一些要求。首先,所有类型都必须订阅 'static
。这是 std::any::TypeId
的要求(因为显然很难区分只有生命周期不同的类型)。此外,这也是一些特质的特质约束。
此代码还意味着,当在枚举定义中使用泛型时,必须添加 std::any::TypeId
特质约束(请参阅上一节中的示例)。
为了让派生宏工作,还必须确保所有字段类型实现 PartialEq
和 Debug
特质。
有关限制和误用的更完整列表,请参阅 tests
文件夹中的 uncompilable_examples
子目录。
已知问题
目前已知的主要问题涉及运行此包的测试套件。执行 cargo test
失败,原因是 trybuild
和 Rust 工作区存在问题。我无法解决这个问题,但通过逐个运行测试 cargo test --package variant_access --test tests {{test name}}
确保所有测试都通过。