Skip to content
On this page

本文参考了Rusticity: convert an integer to an enum

我正在学习Rust编程。在Rust中思考是一种令人愉快的体验,我越练习Rust,我越觉得它能更好地让开发人员能够自信地解决复杂的问题。

然而,我有时却会感到沮丧。例如,有些事情在C或Python中可以轻松完成,但是在Rust中却需要做更多工作。不久前,当我不得不将整数转换为一个enum时,就发生了这种事。让我们看看这在C中通常是如何完成的,以及如何在Rust中完成。

在C中将整数转换为项

在C中,枚举常量是int类型 。因此,整数值可以直接分配给一个enum.

c
#include <stdio.h>

enum atomic_number {
    HYDROGEN = 1,
    HELIUM = 2,
    // ...
    IRON = 26,
};

int main(void)
{
    enum atomic_number element = 26;

    if (element == IRON) {
        printf("Beware of Rust!\n");
    }

    return 0;
}

虽然将整数值分配给一个enum很容易,但C编译器不执行边界检查。没有什么能阻止我们为一个atomic_number分配一个不可能的值。

Rust中将整数转换为enum

rust
enum AtomicNumber {
    HYDROGEN = 1,
    HELIUM = 2,
    // ...
    IRON = 26,
}

fn main() {
    let element: AtomicNumber = 26;
}

当我们尝试编译和运行程序cargo run时,Rust编译器报告不匹配的类型错误:

error[E0308]: mismatched types
 --> src/main.rs:9:34
  |
9 |     let element: AtomicNumber = 26;
  |                                 ^^ expected enum `AtomicNumber`, found integral variable
  |
  = note: expected type `AtomicNumber`
             found type `{integer}`

编译器错误清楚地指示integer和AtomicNumber是两种不同的类型。

为了显式将整数转换为AtomicNumber,我们可以编写一个转换函数,该函数以u32作为参数并返回AtomicNumber

rust
enum AtomicNumber {
    HYDROGEN = 1,
    HELIUM = 2,
    // ...
    IRON = 26,
}

impl AtomicNumber {
    fn from_u32(value: u32) -> AtomicNumber {
        match value {
            1 => AtomicNumber::HYDROGEN,
            2 => AtomicNumber::HELIUM,
            // ...
            26 => AtomicNumber::IRON,
            _ => panic!("Unknown value: {}", value),
        }
    }
}

fn main() {
    let element = AtomicNumber::from_u32(26);
}

from_u32函数的类型是关联函数,因为它仅在此类型的上下文中定义,他第一个参数不是self.

关于from_u32()有几个问题:

  • 当给定值与enum中的值不匹配时,使用panic!()
  • 必须针对整数值逐一转换
  • 如果枚举列表很长,则转换函数将变得很长。

由于AtomicNumber有100多个成员,实现转换函数很快就会变得枯燥乏味。应该有更好的办法。

更好的转换方式

更优雅的解决方案是使用num_enum中的 FromPrimitive特性,以及来自num_enum的语法扩展。

Cargo.toml中,添加依赖项:

toml
[dependencies]
num_enum = "0.5.1"

然后,使用#[derive]属性:

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

#[derive(TryFromPrimitive)]
#[repr(u32)]
enum AtomicNumber {
    HYDROGEN = 1,
    HELIUM = 2,
    // ...
    IRON = 26,
}

fn main() {
    let element = AtomicNumber::try_from(26u32);
    match element {
        Some(AtomicNumber::IRON) => println!("Beware of Rust!"),
        Some(_) => {},
        None => println!("Unknown atomic number")
    }
}

#[derive(FromPrimitive)]属性指示Rust编译器为enum生成try_from实现。 它的好处是:

  • 解决了枯燥无味的编码
  • 不使用panic,而是使用TryFrom,更安全

有了#[derive(FromPrimitive)]这个属性,我们的Rust程序几乎和用C编写的等效程序一样简洁,并且更加安全。

Rust让编码更快乐

Rust强大的生态,能够让我们专注解决问题而不是各种语言的细枝末节问题.