29次发布

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.1.1 2018年11月12日

#12 in Rust模式

Download history 543691/week @ 2024-05-04 594378/week @ 2024-05-11 569587/week @ 2024-05-18 557216/week @ 2024-05-25 676624/week @ 2024-06-01 638378/week @ 2024-06-08 604353/week @ 2024-06-15 596690/week @ 2024-06-22 680981/week @ 2024-06-29 766333/week @ 2024-07-06 689474/week @ 2024-07-13 720635/week @ 2024-07-20 761876/week @ 2024-07-27 810861/week @ 2024-08-03 810876/week @ 2024-08-10 724274/week @ 2024-08-17

3,241,100 每月下载量
用于 5,812 个crate(613个直接使用)

BSD-3-Clause OR MIT OR Apache-2.0

25KB
158

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

num_enumIntoPrimitive 比使用 as 更类型安全,因为 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),
    );
}

默认变体

有时在枚举中添加一个充当 wildcard 匹配尚未被其他变体覆盖的所有值的 Other 变体是有用的。

使用 #[num_enum(default)] 属性(或标准库中的 #[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!
}

请注意,这个派生函数忽略了枚举上任何 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 条款、Apache 2 或 MIT 许可证下使用。

依赖项

~0.3–1MB
~23K SLoC