4 个版本 (1 个稳定版)
1.0.0 | 2024 年 3 月 4 日 |
---|---|
0.9.3 | 2023 年 12 月 4 日 |
0.9.1 | 2023 年 11 月 29 日 |
0.8.2 |
|
#337 in Rust 模式
79KB
374 行
您正在阅读 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_variant 和 Valued::value_to_variant_opt 的委托函数。
- 序列化和反序列化特性:这些特性允许将此枚举序列化和反序列化为其判别值,这对于枚举变体没有字段的情况非常有用。
特性 Serialize 和 Deserialize 与 serde 的 Serialize 和 DeserializeOwned 特性匹配,要使用这些特性,您必须在 Cargo.toml 中添加特性 serde_enums,例如:indexed_valued_enums = { version = "1.0.0", features=["serde_enums"] }
特性 NanoSerBin、NanoDeBin、NanoSerJson 和 NanoDeJson 分别实现了 nanoserde 的 SerBin、DeBin、SerJson 和 DeJson 特性。
重要:当使用这些序列化和反序列化特性时,它将尝试覆盖 您的 依赖项实现它们,这意味着 indexed_valued_enums 不会直接依赖 Serde 或 NanoSerde 来实现这些接口,因此如果您想使用 nanoserde 的序列化和反序列化方法,则 nanoserde 必须是您的 Cargo.toml 中的依赖项,这样,您始终可以控制正在应用哪个版本的 Serde 和 NanoSerde。
此 crate 做的 4 个假设
- 您不会重命名此 crate 的名称或 附加特性 中使用的任何名称,这是因为当展开宏时,它将尝试针对 您的 依赖项进行目标定位,通过这种方式,您避免了此 crate 和您的 crate 使用不同版本时的较长的编译时间,您可能需要的依赖项包括:
serde
、nanoserde
和const-default
。 - 您的枚举变体没有手动设置其判别值,这是因为存储在数组中的这些变体的值,其中每个值都存储在其变体的位置对应的索引中,因此判别值作为索引。
- 枚举具有属性 #[repr(usize)],您不需要手动执行此操作,声明性宏会自动执行,当使用属性 '#[enum_valued_as(您的类型)]' 时,它静默地添加 #[repr(usize)],但如果您要使用 cargo expand 并使用原始代码,则必须保留 #[repr(usize)] 属性。
依赖项
~0–285KB