Rusticity: convert an integer to an enum
I am learning how to program in Rust. Thinking in Rust is a delightful experience and the more I practice Rust the more I feel how it empowers developers to solve complex problems with confidence.
However, I sometimes get frustrated on my way to rusticity. For instance, when a programming task easily done in C or Python requires more work in Rust. I experienced this when trying to convert an integer to an enum. This blog post compares how this is usually done in C and how to do it safely in Rust.
Converting an integer to an enum in C
In C, the enumeration constants have the type int
. Thus, an integer
value can be directly assigned to an enum.
#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; }
While it is easy to assign an integer value to an enum, the C compiler
performs no bounds checking. Nothing prevents us from assigning an
impossible value to an atomic_number
enum.
Converting an integer to an enum in Rust, the naïve way
Let's write a similar program in Rust:
enum AtomicNumber { HYDROGEN = 1, HELIUM = 2, // ... IRON = 26, } fn main() { let element: AtomicNumber = 26; }
When we try to compile and run the program with cargo run
, the Rust
compiler reports a mismatched types error:
error[E0308]: mismatched types --> src/main.rs:9:33 | 9 | let element: AtomicNumber = 26; | ------------ ^^ expected enum `AtomicNumber`, found integer | | | expected due to this
The compiler error clearly indicates that AtomicNumber
and integer
are two different types.
To explicitly convert an integer to our AtomicNumber
enum, we can
write a conversion function that takes an unsigned 32-bits integer as
parameter and returns an AtomicNumber
.
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); }
The from_u32()
function is an associated function of the
AtomicNumber
type because it is associated with that type,
and unlike a method does not take a first parameter named self
.
There are several issues in from_u32()
:
- When the given value does not match any variant in the enumeration,
panic!()
terminates the program. - Both the enumeration definition and the conversion function match enumeration variants with integers. This duplication is error-prone.
- The conversion function grows with the number of variants.
The periodic table contains more than 100 elements. Implementing a conversion function for atomic numbers is boring and error-prone. There is a better way.
Converting an integer to an enum in Rust with num-derive
A more elegant solution is to use the FromPrimitive trait from the num crate coupled with syntax extensions from the num-derive crate.
In Cargo.toml
, add dependencies for
num,
num-derive, and
num-traits:
[dependencies] num = "0.4" num-derive = "0.4" num-traits = "0.2"
Then, use the #[derive]
attribute:
extern crate num; #[macro_use] extern crate num_derive; #[derive(FromPrimitive)] enum AtomicNumber { HYDROGEN = 1, HELIUM = 2, // ... IRON = 26, } fn main() { let element = num::FromPrimitive::from_u32(26); match element { Some(AtomicNumber::IRON) => println!("Beware of Rust!"), Some(_) => {}, None => println!("Unknown atomic number") } }
The #[derive(FromPrimitive)]
attribute instructs the Rust compiler
to generate a basic implementation of the FromPrimitive
trait for
the AtomicNumber
enumeration. Unlike our handwritten from_u32()
function, the conversion function generated by the Rust compiler
returns an Option
which is
either Some
atomic number or None
if the given integer does not
match any known atomic number. This is much safer than calling
panic!()
.
With the #[derive(FromPrimitive)]
attribute our Rust program is
nearly as concise as the same program written in C with the bonus
of being safer.
Harder, better, safer, rustier
While I had some hard time figuring how to convert an integer value to an enum variant in Rust, I feel reassured by its type safety and pleased by its ecosystem of crates.