#枚举 #num #numbers #原始类型 #repr #zero #variant

无std num_enum_derive

::num_enum(进程宏,简化原始类型与枚举之间的互操作)的内部实现细节

22个版本

0.7.3 2024年7月29日
0.7.2 2024年1月7日
0.7.1 2023年10月27日
0.6.1 2023年4月15日
0.4.2 2019年10月4日

进程宏中排名1216

Download history 559144/week @ 2024-04-22 513823/week @ 2024-04-29 552020/week @ 2024-05-06 585822/week @ 2024-05-13 571247/week @ 2024-05-20 562514/week @ 2024-05-27 673357/week @ 2024-06-03 640840/week @ 2024-06-10 600549/week @ 2024-06-17 598345/week @ 2024-06-24 680675/week @ 2024-07-01 770191/week @ 2024-07-08 684772/week @ 2024-07-15 717146/week @ 2024-07-22 775167/week @ 2024-07-29 807992/week @ 2024-08-05

每月下载量3,014,993
用于5,740个crate(2个直接使用)

BSD-3-Clause OR MIT OR Apache-2.0

60KB
1K SLoC

num_enum

使用进程宏简化原始类型与枚举之间的互操作。此crate与no_std兼容。

crates.io Documentation Build Status

将枚举转换为原始类型

use num_enum::IntoPrimitive;

#[derive(IntoPrimitive)]
#[repr(u8)]
enum Number {
    Zero,
    One,
}

fn main() {
    let zero: u8 = Number::Zero.into();
    assert_eq!(zero, 0u8);
}

与使用 as 相比,num_enumIntoPrimitive 更具有类型安全性,因为 as 会静默截断 - num_enum 仅从枚举的区分类型导出 From

尝试使用 try_from 将原始类型转换为枚举

use num_enum::TryFromPrimitive;
use std::convert::TryFrom;

#[derive(Debug, Eq, PartialEq, TryFromPrimitive)]
#[repr(u8)]
enum Number {
    Zero,
    One,
}

fn main() {
    let zero = Number::try_from(0u8);
    assert_eq!(zero, Ok(Number::Zero));

    let three = Number::try_from(3u8);
    assert_eq!(
        three.unwrap_err().to_string(),
        "No discriminant in enum `Number` matches the value `3`",
    );
}

变体选项

有时单个枚举变体可能可以用多个数值表示。

使用 #[num_enum(alternatives = [..])] 属性,可以为单个变体定义额外的值选项。

(此属性不会影响 IntoPrimitive 的行为,它将始终返回规范值。)

use num_enum::TryFromPrimitive;
use std::convert::TryFrom;

#[derive(Debug, Eq, PartialEq, TryFromPrimitive)]
#[repr(u8)]
enum Number {
    Zero = 0,
    #[num_enum(alternatives = [2])]
    OneOrTwo = 1,
}

fn main() {
    let zero = Number::try_from(0u8);
    assert_eq!(zero, Ok(Number::Zero));

    let one = Number::try_from(1u8);
    assert_eq!(one, Ok(Number::OneOrTwo));

    let two = Number::try_from(2u8);
    assert_eq!(two, Ok(Number::OneOrTwo));

    let three = Number::try_from(3u8);
    assert_eq!(
        three.unwrap_err().to_string(),
        "No discriminant in enum `Number` matches the value `3`",
    );
}

还支持范围表达式作为替代方案,但需要启用 complex-expressions 功能

use num_enum::TryFromPrimitive;
use std::convert::TryFrom;

#[derive(Debug, Eq, PartialEq, TryFromPrimitive)]
#[repr(u8)]
enum Number {
    Zero = 0,
    #[num_enum(alternatives = [2..16])]
    Some = 1,
    #[num_enum(alternatives = [17, 18..=255])]
    Many = 16,
}

fn main() {
    let zero = Number::try_from(0u8);
    assert_eq!(zero, Ok(Number::Zero));

    let some = Number::try_from(15u8);
    assert_eq!(some, Ok(Number::Some));

    let many = Number::try_from(255u8);
    assert_eq!(many, Ok(Number::Many));
}

自定义错误类型

TryFromPrimitive 默认将使用 num_enum::TryFromPrimitiveError 作为其 Error 类型。

如果您想使用不同的类型,可以使用注解来实现

use num_enum::TryFromPrimitive;

#[derive(Debug, Eq, PartialEq, TryFromPrimitive)]
#[num_enum(error_type(name = CustomError, constructor = CustomError::new))]
#[repr(u8)]
enum FirstNumber {
    Zero,
    One,
    Two,
}

struct CustomError {}

impl CustomError {
    fn new(value: u8) -> CustomError {
        CustomError {}
    }
}

使用 from_primitive 安全地将原始类型转换为穷举枚举

如果您的枚举包含所有可能的原始值,则可以为其派生 FromPrimitive(这将自动实现 stdlib 的 From

您可以通过以下方式覆盖所有可能的值

  • 为每个可能的值拥有变体
  • 拥有一个标记为 #[num_enum(default)] 的变体
  • 拥有一个标记为 #[num_enum(catch_all)] 的变体
  • 拥有覆盖变体未覆盖的值的 #[num_enum(alternatives = [...])
use num_enum::FromPrimitive;

#[derive(Debug, Eq, PartialEq, FromPrimitive)]
#[repr(u8)]
enum Number {
    Zero,
    #[num_enum(default)]
    NonZero,
}

fn main() {
    assert_eq!(
        Number::Zero,
        Number::from(0_u8),
    );
    assert_eq!(
        Number::NonZero,
        Number::from(1_u8),
    );
}

默认变体

有时希望在枚举中有一个 Other 变体,它充当一个通配符,匹配其他尚未被其他变体覆盖的值。

#[num_enum(default)] 属性(或 stdlib 的 #[default] 属性)允许您将变体标记为默认值。

(此属性不会影响 IntoPrimitive 的行为,它将始终返回规范值。)

use num_enum::FromPrimitive;
use std::convert::TryFrom;

#[derive(Debug, Eq, PartialEq, FromPrimitive)]
#[repr(u8)]
enum Number {
    Zero = 0,
    #[num_enum(default)]
    NonZero = 1,
}

fn main() {
    let zero = Number::from(0u8);
    assert_eq!(zero, Number::Zero);

    let one = Number::from(1u8);
    assert_eq!(one, Number::NonZero);

    let two = Number::from(2u8);
    assert_eq!(two, Number::NonZero);
}

只有 FromPrimitive 关注 default 属性,TryFromPrimitive 忽略它们。

捕获所有变体

有时希望在变体中有一个 Other 变体,它将未匹配的值作为字段持有。

#[num_enum(catch_all)] 属性允许您最多标记一个变体用于此目的。应用此属性的变体必须是具有恰好一个字段与 repr 类型匹配的元组变体。

use num_enum::FromPrimitive;
use std::convert::TryFrom;

#[derive(Debug, Eq, PartialEq, FromPrimitive)]
#[repr(u8)]
enum Number {
    Zero = 0,
    #[num_enum(catch_all)]
    NonZero(u8),
}

fn main() {
    let zero = Number::from(0u8);
    assert_eq!(zero, Number::Zero);

    let one = Number::from(1u8);
    assert_eq!(one, Number::NonZero(1_u8));

    let two = Number::from(2u8);
    assert_eq!(two, Number::NonZero(2_u8));
}

由于这是自然穷举的,因此这仅支持 FromPrimitive,不支持 TryFromPrimitive

使用 unchecked_transmute_from 不安全地将原始类型转换为枚举

如果您非常确定转换将会成功(并且没有使用过任何变体的 #[num_enum(default)]#[num_enum(alternatives = [..])]),并且想要避免一些小的开销,您可以使用不安全的代码来完成这个转换。除非您有数据表明上面 try_from 产生的匹配语句是您的瓶颈,否则您应该避免这样做,因为不安全的代码可能会在您的程序中导致严重的内存问题。

use num_enum::UnsafeFromPrimitive;

#[derive(Debug, Eq, PartialEq, UnsafeFromPrimitive)]
#[repr(u8)]
enum Number {
    Zero,
    One,
}

fn main() {
    assert_eq!(
        unsafe { Number::unchecked_transmute_from(0_u8) },
        Number::Zero,
    );
    assert_eq!(
        unsafe { Number::unchecked_transmute_from(1_u8) },
        Number::One,
    );
}

unsafe fn undefined_behavior() {
    let _ = Number::unchecked_transmute_from(2); // 2 is not a valid discriminant!
}

请注意,此 derive 会忽略枚举上的任何 defaultcatch_allalternatives 属性。如果您需要支持从这些值进行转换,您应该使用 TryFromPrimitiveFromPrimitive

这意味着,例如,以下是不确定的行为

use num_enum::UnsafeFromPrimitive;

#[derive(UnsafeFromPrimitive)]
#[repr(u8)]
enum Number {
    Zero = 0,

    // Same for `#[num_enum(catch_all)]`, and `#[num_enum(alternatives = [2, ...])]`
    #[num_enum(default)]
    One = 1,
}
let _undefined_behavior = unsafe { Number::unchecked_transmute_from(2) };

可选功能

一些枚举值可能由复杂表达式组成,例如

enum Number {
    Zero = (0, 1).0,
    One = (0, 1).1,
}

为了减少编译时间,默认情况下不支持这些功能,但如果您启用了对 num_enum 的依赖项的 complex-expressions 功能,这些功能应该开始工作。

许可证

num_enum 可以根据您的选择在 BSD 3-clause、Apache 2 或 MIT 许可证下使用。

依赖关系

~0.3–1MB
~23K SLoC