8 个版本
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 日 |
#7 在 #内部
每月 35 次下载
23KB
98 行
Variant Access
一组特质和宏,用于定义基于 C++ 标准库中 std::variant api 的 Rust 枚举的通用 API
基本用法
考虑以下枚举
enum Enum {
F1(i32),
F2(bool)
}
当然,我们可以直接使用这种枚举,使用字段名称和匹配语句。此 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)
。此示例回到了此软件包最初的动力。
类型要求
为了使特质和/或派生宏正常工作,您的枚举定义必须满足一些要求。首先,所有类型都必须订阅 'static
。这是 std::any::TypeId
的要求(因为显然很难区分仅在不同生命周期中有所不同的类型)。此外,这是某些 variant_access 特质的特质约束。
这也意味着,当在枚举定义中使用泛型时,您必须添加 'static
特质约束(请参阅上一节的示例)。
为了使派生宏正常工作,枚举的所有字段类型都必须实现 PartialEq
和 Debug
特质。
有关限制和误用的更完整列表,请参阅 tests
文件夹中的 uncompilable_examples
子目录。
已知问题
目前,已知的主要问题仅涉及运行此crate的测试套件。由于 trybuild
和 Rust 工作区的问题,cargo test
失败。我无法解决这个问题,但通过逐个运行测试,即使用 cargo test
--package variant_access --test tests {{test name}} 确保所有测试都通过。
依赖项
~8–17MB
~228K SLoC