#枚举 #判别式 #索引 #宏推导 #声明宏 #无分配 #

无 std indexed_valued_enums

创建将值解析为枚举的枚举,并可通过其值或其判别式获取其变体,受 Java 启发。

4 个版本 (1 个稳定版)

1.0.0 2024 年 3 月 4 日
0.9.3 2023 年 12 月 4 日
0.9.1 2023 年 11 月 29 日
0.8.2 2023 年 11 月 29 日

#337 in Rust 模式

自定义许可

79KB
374

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 此包假设

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;
    }
}

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

    在下面的示例中,Planet::Mars 给出判别值 1,而判别值 1 会返回 Planet::Mars。


  • [值] 允许您将值与判别值关联起来,为变体提供一个返回关联常量的 'value' 函数,以及一个 'value_to_variant_opt' 函数来获取一个可能变体,其常量与指定值相匹配。

    在下面的示例中,Planet::Earth 返回值为 CelestialBody{半径:6357.0,重力: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
}


在这些字段中,您可以指定不同的参数以更改枚举的实现

  • 枚举名称:枚举将具有的名称。
  • 值类型:变体解析到的值的类型。
  • 变体,值:创建的变体的名称及其解析到的名称,值必须是常量并且具有 'static 生存期。
  • 功能:您希望枚举使用的特定实现列表,有关更多信息,请参阅附加功能部分。

注意:您可以在每个 变体,值 对、以及枚举之前写入元数据(例如 #[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 宏使用值枚举的入门示例

这创建了一个公共枚举,其中每个数字都有一个关联的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'实现clone,避免Derive Clone的大规模扩展,然而这不会克隆变体的字段(如果有的话),在大型无字段枚举的情况下非常理想。
    由于它调用'articulate'然后'from_discriminant',这个操作是O(1)。

  • 委托者:实现与[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 特性。

    重要:当使用这些序列化和反序列化特性时,它将尝试覆盖 您的 依赖项实现它们,这意味着 indexed_valued_enums 不会直接依赖 Serde 或 NanoSerde 来实现这些接口,因此如果您想使用 nanoserde 的序列化和反序列化方法,则 nanoserde 必须是您的 Cargo.toml 中的依赖项,这样,您始终可以控制正在应用哪个版本的 Serde 和 NanoSerde。

此 crate 做的 4 个假设

  • 您不会重命名此 crate 的名称或 附加特性 中使用的任何名称,这是因为当展开宏时,它将尝试针对 您的 依赖项进行目标定位,通过这种方式,您避免了此 crate 和您的 crate 使用不同版本时的较长的编译时间,您可能需要的依赖项包括: serdenanoserdeconst-default

  • 您的枚举变体没有手动设置其判别值,这是因为存储在数组中的这些变体的值,其中每个值都存储在其变体的位置对应的索引中,因此判别值作为索引。

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

依赖项

~0–285KB