Appearance
本文参考了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强大的生态,能够让我们专注解决问题而不是各种语言的细枝末节问题.