Question
Given this Rust enum, it is possible to obtain a variant's integer discriminant with a cast:
enum MyEnum {
A = 1,
B,
C,
}
let x = MyEnum::C as i32;
However, matching the integer against enum variants does not compile:
match x {
MyEnum::A => {}
MyEnum::B => {}
MyEnum::C => {}
_ => {}
}
How can an integer be matched against an enum's discriminant values, or safely converted back into MyEnum? Is there an idiomatic conversion method similar to from that can return Option<MyEnum>?
Short Answer
You will learn why an integer and an enum are different Rust types, how to match integer discriminants correctly, and how to create a safe integer-to-enum conversion with TryFrom. You will also see why directly reinterpreting an arbitrary integer as an enum is unsafe.
Concept
An enum variant such as MyEnum::A is a value whose type is MyEnum. Even when that variant has a numeric discriminant such as 1, it is not itself an i32.
let value: i32 = 1;
let variant: MyEnum = MyEnum::A;
Because these values have different types, Rust cannot use MyEnum::A as a pattern while matching an i32.
A discriminant is the number assigned to a fieldless enum variant. In this example, the values are:
| Variant | Discriminant |
|---|---|
MyEnum::A | 1 |
MyEnum::B | 2 |
MyEnum::C | 3 |
Casting an enum to an integer is one-way syntax:
let number = MyEnum::C as i32;
Rust does not provide the reverse as MyEnum conversion for arbitrary integers because an integer might not correspond to any valid variant. A safe program must handle invalid values, usually with Result through the standard TryFrom trait, or sometimes with Option in a custom helper method.
Use #[repr(i32)] when the numeric representation is part of an external contract, such as a file format, network protocol, database column, or FFI boundary. It makes the intended integer representation explicit.
Mental Model
Think of an enum as a set of named, approved ID cards:
MyEnum::Ais the approved card named A.- Its printed ID number is
1. - An
i32value is just a number someone hands you.
Seeing the number 1 does not automatically turn it into the approved MyEnum::A card. Your code must check whether the number belongs to the approved set and then return the corresponding variant.
Matching an i32 with MyEnum::A is like comparing a raw number to an ID card: they are different kinds of things. Match the number 1, or convert the number into an enum first.
Syntax and Examples
Declare a fieldless enum with explicit integer representation and implement TryFrom<i32> for safe conversion:
use std::convert::TryFrom;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
enum MyEnum {
A = 1,
B,
C,
}
impl TryFrom<i32> for MyEnum {
type Error = ();
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
1 => Ok(MyEnum::A),
2 => Ok(MyEnum::B),
3 => Ok(MyEnum::C),
_ => Err(()),
}
}
}
fn main() {
let x = MyEnum::C as i32;
match MyEnum::try_from(x) {
Ok(MyEnum::A) => println!("A"),
Ok(MyEnum::B) => println!("B"),
Ok(MyEnum::C) => println!("C"),
Err(()) => println!("Unknown value"),
}
}
The conversion produces Result<MyEnum, ()>:
Ok(MyEnum::C)for3Err(())for an invalid value such as99
Once conversion succeeds, the match expression operates on MyEnum, so enum variant patterns work normally.
If you only need to match the integer and do not need an enum value, match integer literals directly:
let x = 2;
match x {
1 => println!("A"),
2 => println!("B"),
3 => println!("C"),
_ => println!("Unknown value"),
}
Step by Step Execution
Consider this conversion:
let raw_value = 3;
let parsed = MyEnum::try_from(raw_value);
match parsed {
Ok(MyEnum::A) => println!("First"),
Ok(MyEnum::B) => println!("Second"),
Ok(MyEnum::C) => println!("Third"),
Err(()) => println!("Invalid enum value"),
}
Execution proceeds as follows:
raw_valueis ani32containing3.MyEnum::try_from(raw_value)runs thematchinside theTryFromimplementation.- The
3arm matches and returnsOk(MyEnum::C). parsednow has typeResult<MyEnum, ()>.- The outer
matchchecks the result. Ok(MyEnum::C)matches the third arm, so the program printsThird.
For raw_value = 42, the conversion reaches _ => Err(()), and the program prints Invalid enum value. This prevents invalid input from becoming an invalid enum value.
Real World Use Cases
Integer-to-enum conversion is useful whenever your program receives numeric codes from outside Rust:
- HTTP or API data: convert numeric status or category codes into meaningful variants.
- Databases: map an integer column such as
role_codeintoRole::Admin,Role::Editor, orRole::Viewer. - Binary files and network protocols: parse documented numeric tags safely.
- Command-line or configuration input: validate a numeric mode before the rest of the program uses it.
- Foreign Function Interface (FFI): translate C library constants into a Rust enum after validating the returned value.
In all of these cases, outside input may be unknown, outdated, malformed, or reserved for a future version. A fallible conversion makes that possibility explicit.
Real Codebase Usage
In production code, keep raw external values at the boundary and convert them as early as possible.
fn handle_mode(raw_mode: i32) -> Result<(), String> {
let mode = MyEnum::try_from(raw_mode)
.map_err(|_| format!("unsupported mode: {raw_mode}"))?;
match mode {
MyEnum::A => println!("Running mode A"),
MyEnum::B => println!("Running mode B"),
MyEnum::C => println!("Running mode C"),
}
Ok(())
}
This pattern has useful properties:
- Validation happens once. The rest of the function receives a valid
MyEnum. - Invalid data gets a clear error. The raw number is preserved in the message.
- Enum matching is exhaustive. When a new variant is added, Rust can point out matches that need updating.
- Named variants improve readability.
MyEnum::Ccommunicates intent better than a repeated3.
For a convenient optional API, a project may add a named helper:
impl MyEnum {
fn from_i32(value: i32) -> Option<Self> {
Self::try_from(value).ok()
}
}
Prefer TryFrom when callers benefit from an error value or when conversion is part of a general Rust API. Use Option when “known or unknown” is all the caller needs.
Common Mistakes
Matching an integer with enum variants
This fails because x is an i32, while MyEnum::A is a MyEnum:
let x: i32 = 1;
match x {
MyEnum::A => {} // does not compile
_ => {}
}
Fix it by matching integer literals, or convert x with TryFrom first.
Assuming as works in both directions
This is not a safe reverse conversion:
// let value = 99 as MyEnum; // not allowed
Rust requires you to decide what to do with invalid values. Implement TryFrom<i32> or a from_i32 method.
Using unsafe conversion for unchecked input
Avoid code that reinterprets an arbitrary integer as an enum, such as std::mem::transmute. Not every integer is a valid enum discriminant, and creating an invalid enum value can cause undefined behavior.
Validate first:
let value = MyEnum::try_from(raw_value)?;
Forgetting to update conversion code
If you add a variant, update the conversion mapping too:
enum MyEnum {
A = 1,
B,
C,
D,
}
Add 4 => Ok(MyEnum::D) to try_from. A direct match on MyEnum is checked for exhaustiveness, but a separate integer mapping needs deliberate maintenance.
Relying on implicit consecutive values as an external format
B and C currently become 2 and 3, but external contracts are clearer when every public code is explicitly documented or assigned. Use #[repr(i32)] when representation matters outside ordinary Rust logic.
Comparisons
| Approach | Input | Output | Best use |
|---|---|---|---|
Cast enum to integer: variant as i32 | Valid enum | Integer | Sending or storing a known variant as a numeric code |
| Match integer literals | Integer | Branch selection | Small local checks where an enum value is unnecessary |
TryFrom<i32> | Integer | Result<MyEnum, Error> | Validating external input with useful error handling |
Custom from_i32 | Integer | Option<MyEnum> | Simple “valid or unknown” lookup |
| Match an enum | Valid enum | Branch selection | Business logic after validation |
Result versus Option:
- Choose
Resultwhen invalid input should be reported, logged, or propagated with?. - Choose
Optionwhen an unknown value is expected and no additional error details are needed.
#[repr(i32)] versus no repr:
- Without a representation attribute, Rust can still cast a fieldless enum to an integer.
- With
#[repr(i32)], you explicitly state that the enum usesi32representation, which is especially useful for FFI and persisted numeric values.
Cheat Sheet
use std::convert::TryFrom;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
enum Status {
Ready = 1,
Running = 2,
Done = 3,
}
impl TryFrom<i32> for Status {
type Error = ();
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
1 => Ok(Self::Ready),
2 => Ok(Self::Running),
3 => Ok(Self::Done),
_ => Err(()),
}
}
}
- Convert enum to integer:
let code = Status::Ready as i32; - Convert integer safely:
let status = Status::try_from(code); - Match raw integers with
1,2, and3, notStatus::Ready. - Match enum variants only after you have a
Statusvalue. - Handle unknown input:
Err(_)or.ok(). - Do not use
transmuteto turn unchecked integers into enums. - Use
#[repr(i32)]when numeric representation is part of an external interface.
FAQ
Can Rust match an i32 against an enum variant?
No. An i32 and an enum variant have different types. Match integer literals, or convert the integer to the enum first.
How do I convert an integer to an enum in Rust?
Implement TryFrom<i32> and return Ok(variant) for known discriminants and Err(...) for unknown values.
Why is TryFrom better than an unchecked cast?
An integer may not identify a real variant. TryFrom validates the value instead of allowing an invalid enum value to be created.
Can I return Option<MyEnum> instead of Result<MyEnum, _>?
Yes. A custom from_i32 method can return Option<Self>. Internally, it can call Self::try_from(value).ok().
Do I need #[repr(i32)]?
It is recommended when the integer representation matters for FFI, a protocol, storage, or another external contract. It also documents your intent clearly.
Can I write MyEnum::A as i32 directly in a match arm?
Do not rely on casts as patterns. The clearest approaches are matching numeric literals or constants, or converting to MyEnum before matching.
What happens if a new enum variant is added?
Update the TryFrom mapping. Matching on a MyEnum afterward remains valuable because Rust can require all variants to be handled.
Mini Project
Description
Build a parser for numeric task-state codes received from a small external system. The program converts each raw integer into a typed Rust enum, prints valid states, and reports invalid codes without crashing.
Goal
Safely translate raw i32 task-state codes into enum variants and handle unknown values.
Requirements
- Define a
TaskStateenum with numeric codes 1, 2, and 3. - Use
#[repr(i32)]and deriveDebug,Clone,Copy,PartialEq, andEq. - Implement
TryFrom<i32>forTaskState. - Process a list containing both valid and invalid integer codes.
- Print a useful message for every code.
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.