#枚举 #特性 #字段名 #变体 # #宏 derive #变体访问

variant_access_derive

用于推导 VariantAccess 特性的过程宏

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 日

#42#字段名

41 每月下载
用于 variant_access

MIT 许可证

34KB
508

变体访问   最新版本

一组特性与宏,用于定义基于 C++ 标准库 std::variant 的 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 架构生成的代码将所有内部值设为私有,并提供获取器和设置器以统一与这些实体交互。

支持的功能

derive 宏能够完全区分类型,甚至那些在不同模块中具有相同名称的类型。通过使用 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)
    }
}

这是有效的,并且各种特质方法可以区分类型 Complexnamespace::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 的原始动机。

类型要求

为了让特质和/或 derive 宏正常工作,您的枚举定义必须满足一些要求。首先,所有类型都必须订阅 'static。这是 std::any::TypeId 的要求(因为显然很难区分仅在生存期上不同的类型)。此外,这是某些 variant_access 特质的特质界限。

这也意味着,当在枚举定义中使用泛型时,您必须添加 'static 特质界限(参见上一节的示例)。

为了让 derive 宏正常工作,还必须确保枚举的所有字段类型实现了 PartialEqDebug 特质。

有关限制和误用的更完整列表,请参阅 tests 文件夹中的 uncompilable_examples 子目录。

已知问题

目前已知的主要问题仅涉及运行此软件包的测试套件。使用 cargo test 命令时,由于 trybuild 和 Rust 工作空间中存在一个问题,导致测试失败。我无法解决这个问题,但通过逐个运行测试(使用命令 cargo test --package variant_access--test tests {{test name}})可以确保所有测试都通过。

依赖项

约7-16MB
约211K SLoC