1个稳定版本

1.0.0 2024年3月4日

#16 in #discriminant


indexed_valued_enums 中使用

自定义许可证

38KB
157

crates.io GitHub Actions Workflow Status docs.rs GitHub License

您正在阅读indexed_valued_enums版本1.0.0的文档

创建可以解析为值并可以通过其值或其区分符获取其变体的枚举,灵感来自Java的枚举。

1 动机和使用
2 通过声明宏创建值枚举的示例
    2.a 通过声明宏
        2.a.1 通过声明宏使用值枚举的入门示例
        2.a.2 如何使用声明宏
        2.a.3 声明宏的其他示例
    2.a 通过derive宏
        2.b.1 通过Derive宏使用值枚举的入门示例
        2.b.2 如何使用Derive宏
        2.b.3 Derive宏的其他示例
3 额外功能
4 此crate所做假设

1 动机和使用

在几种编程语言中,可以在编译时创建枚举并关联一些信息,例如Java或C#允许获取变体的标识符,并通过该标识符获得变体,它还允许对它们应用构造函数,使得将常量值关联到每个变体变得很容易,允许定义像这样的枚举。

public enum Planet {
    Earth(6357.0, 9.807), Mars(3389.5, 3.71), Mercury(2439.7, 3.7);

    private Double radius;
    private Double gravity;

    Planet(Double radius, Double gravity) {
        this.radius = radius;
        this.gravity = gravity;
    }

    public Double getRadius() {
        return radius;
    }

    public Double getGravity() {
        return gravity;
    }
}

为了复制这些机制,创建了两个特质。
  • [Indexed] 允许您通过函数'discriminant'获取所述变体的判别符/索引,并使用函数'from_discriminant'获取该变体。

    在下面的示例中,Planet::Mars的判别符为1,判别符1将返回Planet::Mars。


  • [Valued] 允许您将值关联到判别符,提供函数'value'以返回与变体关联的常量,并提供'value_to_variant_opt'以获取可能变体的常量与该值匹配。

    在下面的示例中,Planet::Earth返回值为CelestialBody{ radius: 6357.0, gravity: 9.807 },并返回Planet::Earth。
use indexed_valued_enums::{Valued, enum_valued_as};

#[derive(PartialEq)]
pub struct CelestialBody {
    radius: f32,
    gravity: f32,
}

#[derive(PartialEq, Debug, Valued)]
#[enum_valued_as(CelestialBody)]
#[enum_valued_features(DerefToValue, Delegators, ValueToVariantDelegators)]
enum Planet {
    #[value(CelestialBody{ radius: 6357.0, gravity: 9.807 })]
    Earth,
    #[value(CelestialBody{ radius: 3389.5, gravity: 3.71 })]
    Mars,
    #[value(CelestialBody{ radius: 2439.7, gravity: 3.7 })]
    Mercury,
}

#[test]
fn example_test(){
    //Identifiers mechanics
    assert_eq!(Planet::Mars, Planet::from_discriminant(1));
    assert_eq!(Planet::Mercury.discriminant(), 2);

    //Value mechanics
    assert_eq!(Planet::Earth.value().radius, 6357.0);
    assert_eq!(Planet::Mars.gravity, 3.71);
    assert_eq!(Planet::Mercury, Planet::value_to_variant(&CelestialBody{ radius: 2439.7, gravity: 3.7 }));
}

您可以使用两种宏之一在枚举上实现此功能。

  • 声明式宏:在这个宏中,您需要编写每个变体及其值,这使得编写和阅读变得非常容易,尤其是当创建没有太多操作的简单枚举时非常有用,无论是短枚举还是长枚举。但是,如果您需要直接操作枚举,它可能非常有限,并且不支持带有字段(无论是命名的还是未命名的)的变体。如果您处于这两种情况中的任何一种,请使用 derive 宏。

  • Derive 宏:在这个宏中,您只需要为枚举及其变体添加一些属性来指示值,从而让您完全控制枚举。然而,过多的变体可能会产生难以阅读的代码,在这种情况下,它们通常是没有任何字段的大型枚举,在这种情况下,声明式宏是完美的。它需要您在 Cargo.toml 中添加 'derive' 功能,如下所示:indexed_valued_enums = { version = "1.0.0", features=["derive", ...] }

2.a.1 通过声明式宏使用值枚举的入门示例

这创建了一个公共枚举,其中每个数字都有一个关联的NumberDescription类型值,就像在入门Derive示例中一样。

use indexed_valued_enums::create_indexed_valued_enum;
use indexed_valued_enums::indexed_enum::Indexed;
use indexed_valued_enums::valued_enum::Valued;

create_indexed_valued_enum! {
    #[derive(Eq, PartialEq, Debug)]
    //The double octothorpe is intentional
    ###[features(Clone)]
    pub enum Number valued as NumberDescription;
    Zero, NumberDescription { description: "Zero position", index: 0 },
    First, NumberDescription { description: "First position", index: 1 },
    Second, NumberDescription { description: "Second position", index: 2 },
    Third, NumberDescription { description: "Third position", index: 3 }
}

#[derive(PartialEq)]
pub struct NumberDescription {
    description: &'static str,
    index: u16,
}

#[test]
fn test() {
    assert_eq!(Number::Zero.discriminant(), 0);
    assert_eq!(Number::First.value().description, "First position");
    assert_eq!(Number::Second.clone(), Number::Second);
    assert_eq!(Number::Third, Number::value_to_variant(
        &NumberDescription { description: "Third position", index: 3 }));
}

2.a.2 如何使用声明式宏

作为宏,您只需遵循此模式

create_indexed_valued_enum!{
     您的元数据 //如 '#[derive(...)]',这是可选的
     ##[features(Feature1, Feature2, ...)] // 这是可选的,但需要两个井号
     可见性 enum 枚举的名称 values as 值的类型;

     变体1的元数据 //这是可选的
     变体1, 值1,

     变体2的元数据 //这是可选的
     变体2, 值2,

     ...

     变体N的元数据 //这是可选的
     变体N, 值N
}


在这些字段中的每一个上,您都可以指示不同的参数来更改枚举的实现。

  • 枚举名称:枚举将具有的名称。
  • 值的类型:变体解析到的值的类型。
  • 变体对 变异体,值:要创建的变异体的名称以及它们解析到的名称,值必须是常量并且具有 '静态生命周期'。
  • 特性:您希望枚举使用的特定实现列表,有关更多信息,请参阅附加特性部分。

注意:您可以在每一对 变异体,值 前以及枚举前编写元数据(例如 #[derive(...)]),但是要求 #[features(...)] 是枚举声明元数据中的最后一个,因为这不是另一个元数据(因此使用双井号来表示)。

2.a.3 声明宏的其他示例

一个简单的例子可能如下所示

use indexed_valued_enums::create_indexed_valued_enum;

create_indexed_valued_enum! {
    //Defines the enum and the value type it resolves to
    pub enum MyOtherNumber valued as &'static str;
    //Defines every variant and their value, note that values must be const
    Zero, "Zero position",
    First, "First position",
    Second, "Second position",
    Third,  "Third position"
}

一个更复杂的例子可能如下所示

use indexed_valued_enums::create_indexed_valued_enum;

create_indexed_valued_enum! {
    #[doc="This is a custom enum that can get values of &'static str!"]
    //This enum derives certain traits, although you don't need to write this
    #[derive(Hash, Ord, PartialOrd, Eq, PartialEq, Debug)]
    //Gives a list of features that are decomposed functions for specific behaviours, you have
    //more details about them down below
    ###[features(Clone, DerefToValue, Delegators, ValueToVariantDelegators)]
    //Defines the enum and the value type it resolves to
    pub enum MyOtherNumber valued as &'static str;
    //Defines every variant and their value, note that values must be const
    Zero, "Zero position",
    First, "First position",
    Second, "Second position",
    Third,  "Third position"
}

2.b.1 通过 Derive 宏使用值枚举的介绍性示例

这创建了一个公共枚举,其中每个 Number 都有一个关联的 NumberDescription 类型的值,就像在声明宏示例中一样。

use indexed_valued_enums::{enum_valued_as, Valued};

#[derive(Eq, PartialEq, Debug, Valued)]
#[enum_valued_as(NumberDescription)]
pub enum Number{
    #[value(NumberDescription { description: "Zero position", index: 0 })]
    Zero,
    #[value(NumberDescription { description: "First position", index: 1 })]
    First,
    #[value(NumberDescription { description: "Second position", index: 2 })]
    Second,
    #[value(NumberDescription { description: "Third position", index: 3 })]
    Third,
}

#[derive(PartialEq)]
pub struct NumberDescription {
    description: &'static str,
    index: u16,
}

#[test]
fn test() {
    assert_eq!(Number::Zero.discriminant(), 0);
    assert_eq!(Number::First.value().description, "First position");
    assert_eq!(Number::Second.clone(), Number::Second);
    assert_eq!(Number::Third, Number::value_to_variant(
        &NumberDescription { description: "Third position", index: 3 }));
}

2.b.2 如何使用 Derive 宏

这需要在您的 Cargo.toml 中指定 'derive' 功能,如下所示

indexed_valued_enums = { version =  "1.0.0", features=["derive", ...] }
```.

基本实现:添加 derive 宏 indexed_valued_enums_derive::Valued,然后编写 #[enum_valued_as(值类型)] 属性,指定您的变异体将解析到的类型,然后在每个变异体上编写 #[value(此变异体的值)] 属性。这样

use indexed_valued_enums::{Valued, enum_valued_as};

#[derive(Valued)]
#[enum_valued_as(u8)]
pub enum MyEnum{
    #[value(10)]
    Variant1,
    #[value(20)]
    Variant2,
}

添加额外功能:在 Derive 声明下方,您可以编写 #[enum_valued_features(您希望的功能)] 属性,这将自动实现某些特性或函数,这些将非常有用,您可以在附加特性部分中检查这些功能。

...
/// Adding 'Delegators' allows to call most of functions at
/// compile-time, being able to get values and variants easily
#[enum_valued_features(DerefToValue, Delegators)]
pub enum MyEnum{
    ...
}

避免重复:对于变异体的值经常重复或不相关的变异体,您可以使用 #[unvalued_default(您的默认值)] 属性,这将使所有这些无值的变异体解析为指定的值。

...
#[unvalued_default(50)]
pub enum MyEnum{
    /// This variant's value will resolve to 10 as it is specified.
    #[value(10)]
    Variant1,
    /// This variant's value will resolve to the default of 50 as a value it is not specified.
    Variant2,
}

带有字段的变异体也可以添加!与声明宏不同,这个宏与带有字段(无论是有名还是无名)的变异体兼容,但它们有一个缺点:由于 Indexed::from_discriminant 函数必须为每个变异体返回一个常量值,因此我们还需要在编译时创建这些具有值的变异体。当这种情况发生时,您有两个选择

  • 使用 #[variant_initialize_uses(您的默认值)] 属性:在这里,您为这些变异体编写默认内容,例如,如果一个是 IP{host: &'static str, port: u16},您可以编写:#[variant_initialize_uses(host: "localhost", port: 8080)].

  • 如果变体的值实现了 const_default::ConstDefault:您可以在 Cargo.toml 中简单地添加 const-default,例如 const-default = { version = "1.0.0" } 当这个变体从 Indexed::from_discriminant 中解析出来时,它将返回具有在 const_default::ConstDefault 中指定的所有字段的值。
...
pub enum MyEnum{
    /// When applying [from::discriminant] to 0, it will return MyEnum::Variant1(23, "Jorge")
    #[variant_initialize_uses(23, "Jorge")]
    Variant1(u8, &'static str),
    /// Since the attribute #[variant_initialize_uses] isn't specified, when applying
    /// [from::discriminant] to 1, it will return MyEnum::Variant2{age: 0}, as ConstDefault
    /// for u8 returns 0
    Variant2{age:u8},
}

2.b.3 derive 宏的其他示例

一个简单的例子可能如下所示

use indexed_valued_enums::{Valued, enum_valued_as};

#[derive(Valued)]
#[enum_valued_as(&'static str)]
pub enum Number{
    #[value("Zero position")]
    Zero,
    #[value("First position")]
    First,
    #[value("Second position")]
    Second,
    #[value("Third position")]
    Third,
}

一个更复杂的例子可能如下所示

use indexed_valued_enums::{Valued, enum_valued_as};

#[derive(Hash, Ord, PartialOrd, Eq, PartialEq, Debug)]
#[derive(Valued)]
#[enum_valued_as(&'static str)]
#[enum_valued_features(Clone, DerefToValue, Delegators, ValueToVariantDelegators)]
#[unvalued_default("My default string")]
pub enum Number{
    /// Zero doesn't have a value, so it's value will resolve to "My default string"
    Zero,
    #[value("First position")]
    First,
    /// Second is a variant with fields: u8 and u16, since it's not specified, when calling
    /// [Indexed::from_discriminant] the values for both will be 0, which are their default
    /// values on [const_default::ConstDefault::DEFAULT]
    #[value("Second position")]
    Second(u8, u16),
    /// Third is a variant with fields: my_age: u8 and my_name:&'static str, as specified,
    /// calling [Indexed::from_discriminant] will result in those fields contanining
    /// my_age: 23, my_name: "Jorge"
    #[variant_initialize_uses(my_age: 23, my_name: "Jorge")]
    #[value("Third position")]
    Third{my_age: u8, my_name:&'static str},
}

3 其他特性

  • DerefToValue:实现了 Deref,将每个变体解析为它们值的静态引用。

  • Clone:通过调用 'from_discriminant' 实现克隆,避免 Derive Clone 的大范围展开,但这不会克隆变体的字段(如果有的话),在大型无字段枚举的情况下非常理想。
    由于它调用 'discriminant' 然后是 'from_discriminant',这个操作是 O(1)。

  • Delegators:实现与 [Indexed] 和 [Valued] 中的方法等效的 const 函数,如 'value(&self)' 或 'from_discriminant(&self)',注意这些委托函数与 [Indexed] 和 [Valued] 特性中的委托函数不同,因为这些委托者是 const 函数。
    请注意,它不委托 'value_to_variant' 和 'value_to_variant_opt' 方法,因为它们需要值的类型实现 [PartialEq],您可以使用具有特征 ValueToVariantDelegators 的功能委托这些方法,但这些委托函数 不是 const

  • ValueToVariantDelegators:实现了调用 Valued::value_to_variantValued::value_to_variant_opt 的委托函数。

  • 序列化和反序列化特性:这些允许将此枚举序列化和反序列化为仅其判别值,这对于枚举变体没有字段的情况很有用。

    特性 SerializeDeserialize 与 serde 的 Serialize 和 DeserializeOwned 特性匹配,要使用这些,您必须在 Cargo.toml 中添加具有特征 serde_enums 的功能,例如:indexed_valued_enums = { version = "1.0.0", features=["serde_enums"] }

    特性 NanoSerBinNanoDeBinNanoSerJsonNanoDeJson 分别实现了 nanoserde 的 SerBin、DeBin、SerJson 和 DeJson 特性。

    重要:当使用这些 De/Serialization 时,它将尝试在 您的 依赖项上实现它们,这意味着 indexed_valued_enums 在实现这些接口时不会直接依赖于 Serde 或 NanoSerde,因此如果您想使用 nanoserde 的 De/Serialization 方法,那么 nanoserde 必须是您的 Cargo.toml 中的依赖项,这样您就可以始终控制应用的 Serde 和 NanoSerde 的版本。

4 这个软件包所做的一些假设

  • 您无法重命名这个crate的名称或额外功能中使用的任何名称,这是因为当展开宏时,它将尝试针对您的依赖项,通过这样做,您可以避免当这个crate和您的crate使用不同版本时编译时间更长。您可能需要的依赖项包括:serdenanoserdeconst-default

  • 您的枚举变体的判别符不是手动设置的,这是因为这些变体的值存储在一个数组中,其中每个值存储在其变体的位置(即判别符)对应的索引处,因此判别符充当索引。

  • 枚举使用#[repr(usize)]属性,您不需要手动进行此操作,声明宏会自动完成,并且当使用属性#[enum_valued_as(您的类型)]时,它会默默地添加#[repr(usize)],但如果您要使用cargo expand并使用原始代码,则必须保留#[repr(usize)]属性。

依赖项

~1.5MB
~34K SLoC