1个稳定版本
1.0.0 | 2024年3月4日 |
---|
#16 in #discriminant
在 indexed_valued_enums 中使用
38KB
157 行
您正在阅读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_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 特性。
重要:当使用这些 De/Serialization 时,它将尝试在 您的 依赖项上实现它们,这意味着 indexed_valued_enums 在实现这些接口时不会直接依赖于 Serde 或 NanoSerde,因此如果您想使用 nanoserde 的 De/Serialization 方法,那么 nanoserde 必须是您的 Cargo.toml 中的依赖项,这样您就可以始终控制应用的 Serde 和 NanoSerde 的版本。
4 这个软件包所做的一些假设
- 您无法重命名这个crate的名称或额外功能中使用的任何名称,这是因为当展开宏时,它将尝试针对您的依赖项,通过这样做,您可以避免当这个crate和您的crate使用不同版本时编译时间更长。您可能需要的依赖项包括:
serde
、nanoserde
和const-default
。 - 您的枚举变体的判别符不是手动设置的,这是因为这些变体的值存储在一个数组中,其中每个值存储在其变体的位置(即判别符)对应的索引处,因此判别符充当索引。
- 枚举使用#[repr(usize)]属性,您不需要手动进行此操作,声明宏会自动完成,并且当使用属性#[enum_valued_as(您的类型)]时,它会默默地添加#[repr(usize)],但如果您要使用cargo expand并使用原始代码,则必须保留#[repr(usize)]属性。
依赖项
~1.5MB
~34K SLoC