Question
In Rust, I can define enum variants that carry data like this:
enum MyEnum {
A(i32),
B(i32),
}
However, I cannot define an enum like this:
enum MyEnum {
A(123),
B(456),
}
Here, 123 and 456 are intended to be constant values for each variant.
Is there a simpler Rust pattern for representing enum variants with fixed constant values, without creating separate structs for each variant and implementing the values manually?
Short Answer
By the end of this page, you will understand why Rust does not allow enum variants like A(123) and B(456), and what to use instead. You will learn the difference between enum payloads and enum discriminants, how to attach fixed meanings to variants, and several practical Rust patterns for working with constant values in enums.
Concept
Rust enums can represent one of several possible variants. Each variant can optionally carry data.
For example:
enum MyEnum {
A(i32),
B(i32),
}
This does not mean that A always contains one specific number. It means:
Acan store anyi32valueBcan store anyi32value
So values like these are valid:
let x = MyEnum::A(123);
let y = MyEnum::A(999);
let z = MyEnum::B(456);
When you write A(i32), you are declaring the type of data that the variant can hold, not a fixed constant.
That is why this is invalid:
enum MyEnum {
A(123),
B(456),
}
Rust expects a type inside tuple-like enum variants, not a literal value.
What to use instead
If each variant should represent a fixed meaning, Rust usually uses one of these patterns:
- Plain enum variants with no data
- Enum discriminants for integer-backed values
- Methods or
matchexpressions to map variants to constants - Associated constants or helper functions
For example:
enum MyEnum {
A,
B,
}
impl MyEnum {
fn value(&self) -> i32 {
match self {
MyEnum::A => 123,
MyEnum::B => 456,
}
}
}
This is the idiomatic way to say:
- variant
Acorresponds to123 - variant
Bcorresponds to456
Why this matters
This distinction is important in real Rust code because enums are often used to model:
- states
- commands
- message types
- configuration options
- error categories
Sometimes a variant needs custom data, and sometimes it only needs a fixed identity. Knowing the difference helps you design cleaner types and avoid fighting the language.
Mental Model
Think of an enum like a set of labeled cards.
A(i32)means: theAcard has a pocket that can hold anyi32Aby itself means: theAcard is just a label
If you want each label to stand for a fixed number, do not try to put the number inside the type definition as a literal. Instead, keep the labels simple and use a lookup rule:
Amaps to123Bmaps to456
So the enum is the name, and a method or match is the legend that tells you what each name means.
Syntax and Examples
1. Enum with associated data
Use this when each instance may carry a different value:
enum MyEnum {
A(i32),
B(i32),
}
fn main() {
let x = MyEnum::A(123);
let y = MyEnum::B(456);
}
Here, 123 and 456 are runtime values stored inside the enum variants.
2. Enum with fixed meanings
Use plain variants and map them to constants:
enum MyEnum {
A,
B,
}
impl MyEnum {
fn value(self) -> i32 {
match self {
MyEnum::A => 123,
MyEnum::B => 456,
}
}
}
fn main() {
let a = MyEnum::A;
println!("{}", a.value()); // 123
}
This is usually the clearest solution.
3. C-like enum with discriminant values
If you want enum variants to have specific integer representations, Rust supports discriminants for fieldless enums:
enum MyEnum {
A = 123,
B = 456,
}
This works only for fieldless enums.
If you need to convert to the integer value:
enum MyEnum {
A = 123,
B = 456,
}
fn main() {
let x = MyEnum::A as i32;
println!("{}", x); // 123
}
This pattern is useful when the enum represents fixed numeric codes.
4. Associated constants as an alternative
Sometimes you do not need an enum at all:
struct MyValues;
impl MyValues {
const A: i32 = 123;
const B: i32 = 456;
}
fn main() {
println!("{}", MyValues::A);
println!("{}", MyValues::B);
}
Use this when you only need named constants, not variant behavior.
Step by Step Execution
Consider this example:
enum MyEnum {
A,
B,
}
impl MyEnum {
fn value(self) -> i32 {
match self {
MyEnum::A => 123,
MyEnum::B => 456,
}
}
}
fn main() {
let item = MyEnum::B;
let number = item.value();
println!("{}", number);
}
Step-by-step
-
enum MyEnum { A, B }- Defines two possible variants.
- Neither variant stores extra data.
-
impl MyEnum { fn value(self) -> i32 { ... } }- Adds a method named
value. - The method returns an
i32based on the variant.
- Adds a method named
-
let item = MyEnum::B;- Creates a value of type
MyEnum. - Its variant is
B.
- Creates a value of type
-
let number = item.value();- Calls the method.
- Inside the
match, Rust checks which variantitemis. - Since it is
MyEnum::B, the method returns456.
-
println!("{}", number);- Prints
456.
- Prints
Key idea
The enum itself stores which variant it is. The method decides what constant that variant corresponds to.
Real World Use Cases
Status and code mappings
Enums often represent named states that correspond to fixed codes:
enum HttpStatusKind {
Ok,
NotFound,
ServerError,
}
impl HttpStatusKind {
fn code(self) -> u16 {
match self {
HttpStatusKind::Ok => 200,
HttpStatusKind::NotFound => 404,
HttpStatusKind::ServerError => 500,
}
}
}
Protocol or message types
A network protocol may use symbolic names in code but fixed numeric values on the wire.
enum MessageType {
Connect,
Disconnect,
}
impl MessageType {
fn id(self) -> u8 {
match self {
MessageType::Connect => 1,
MessageType::Disconnect => 2,
}
}
}
Configuration modes
An app might use variants for readable logic and methods for fixed settings.
enum RetryMode {
Fast,
Safe,
}
impl RetryMode {
fn max_retries(self) -> u32 {
match self {
RetryMode::Fast => 2,
RetryMode::Safe => 5,
}
}
}
Database or API constants
Enums help avoid magic numbers in code while still letting you convert to the required constant values when needed.
Real Codebase Usage
In real Rust projects, developers usually choose one of these patterns depending on intent.
1. Fieldless enum + method
This is the most common when you want readable variants and fixed derived values.
enum LogLevel {
Debug,
Info,
Error,
}
impl LogLevel {
fn priority(self) -> u8 {
match self {
LogLevel::Debug => 10,
LogLevel::Info => 20,
LogLevel::Error => 30,
}
}
}
Why it is common:
- easy to read
- easy to extend
- works well with
match - avoids scattered magic numbers
2. Validation and conversion methods
Enums often expose helper methods like as_code, to_id, or from_code.
enum Role {
Admin,
User,
}
impl Role {
fn as_code(self) -> u8 {
match self {
Role::Admin => 1,
Role::User => 2,
}
}
fn from_code(code: u8) -> Option<Self> {
match code {
1 => Some(Role::Admin),
2 => Some(Role::User),
_ => None,
}
}
}
This pattern is common in parsing, API integration, and serialization boundaries.
3. Guarding against invalid values
Using an enum prevents arbitrary integers from spreading through your codebase.
Instead of this:
fn handle_role(role: u8) {
if role == 1 {
println!("admin");
}
}
Prefer this:
enum Role {
Admin,
User,
}
fn handle_role(role: Role) {
match role {
Role::Admin => println!("admin"),
Role::User => println!("user"),
}
}
That makes invalid states harder to represent.
4. Discriminants for interop
If exact integer representation matters, developers may use fieldless enums with explicit discriminants, often together with #[repr(...)].
#[repr(u16)]
enum ErrorCode {
NotFound = 404,
Forbidden = 403,
}
This is especially useful for:
- FFI
- binary protocols
- file formats
- low-level systems code
Common Mistakes
Mistake 1: Treating tuple variant fields as fixed values
Broken code:
enum MyEnum {
A(123),
B(456),
}
Why it fails:
- Rust expects a type like
i32, not a literal like123.
Fix:
enum MyEnum {
A,
B,
}
impl MyEnum {
fn value(self) -> i32 {
match self {
MyEnum::A => 123,
MyEnum::B => 456,
}
}
}
Mistake 2: Using associated data when no variable data is needed
Less ideal:
enum MyEnum {
A(i32),
B(i32),
}
let x = MyEnum::A(123);
This allows MyEnum::A(999), which may not match your intent.
Use a fieldless enum if the value should be fixed.
Mistake 3: Confusing discriminants with stored payload data
This is valid:
enum MyEnum {
A = 123,
B = 456,
}
But this does not mean the enum stores an extra i32 field like A(123). It defines the variant's numeric discriminant.
Mistake 4: Casting enums without thinking about representation
enum MyEnum {
A = 123,
B = 456,
}
let x = MyEnum::A as i32;
This is fine for simple fieldless enums, but in low-level or interop code, prefer an explicit representation:
#[repr(i32)]
enum MyEnum {
A = 123,
B = 456,
}
Mistake 5: Using constants when an enum would be safer
If values represent a closed set of choices, an enum is usually better than loose constants.
Less safe:
const ADMIN: u8 = 1;
const USER: u8 = 2;
Safer:
enum Role {
Admin,
User,
}
Comparisons
| Pattern | Example | Best when | Notes |
|---|---|---|---|
| Enum with payload | A(i32) | each variant carries variable data | A(1) and A(999) are both valid |
| Fieldless enum + method | A, B + value() | each variant maps to a fixed meaning | most idiomatic for fixed per-variant values |
| Fieldless enum + discriminants | A = 123 | you need explicit numeric codes | useful for casting and interop |
| Associated constants | const A: i32 = 123 | you only need named constants | no enum behavior or pattern matching |
Fieldless enum vs tuple enum
enum WithData {
A(i32),
}
enum WithoutData {
A,
}
WithData::A(5)stores dataWithoutData::Ais only a variant name
Method mapping vs discriminant
| Approach | Example | Advantage | Drawback |
|---|---|---|---|
| Method mapping | fn value() -> i32 | flexible, readable, can compute values | requires a match |
| Discriminant | A = 123 | direct numeric identity | only for fieldless enums |
If you only want a fixed integer code, discriminants are simple. If you want richer logic or future flexibility, a method is often better.
Cheat Sheet
// Variable data per variant
enum MyEnum {
A(i32),
B(i32),
}
// Fixed meaning per variant
enum MyEnum2 {
A,
B,
}
impl MyEnum2 {
fn value(self) -> i32 {
match self {
MyEnum2::A => 123,
MyEnum2::B => 456,
}
}
}
// Explicit discriminants for fieldless enums
enum MyEnum3 {
A = 123,
B = 456,
}
Rules to remember
A(i32)meansAcarries a value of typei32A(123)is invalid in an enum definition because123is a value, not a type- Use
A = 123only for fieldless enums - Use a method like
value()when variants should map to fixed constants - Use enums when you want a closed set of choices
- Use associated constants when you only need named values
Quick decision guide
- Need variable data? Use
Variant(Type) - Need a fixed code per variant? Use a fieldless enum plus a method or discriminant
- Need only constants? Use
constor associated constants
FAQ
Can I write A(123) directly inside a Rust enum definition?
No. Inside A(...) in an enum definition, Rust expects a type, not a literal value.
How do I give each enum variant a fixed integer in Rust?
Use a fieldless enum with explicit discriminants:
enum MyEnum {
A = 123,
B = 456,
}
Or use a method that returns the desired value.
What is the difference between A(i32) and A = 123?
A(i32)means the variant stores ani32A = 123means the variant has discriminant value123
These are different concepts.
When should I use a method instead of discriminants?
Use a method when you want a readable mapping, extra flexibility, or values that may later depend on more logic.
Are enum discriminants only for fieldless enums?
Yes, explicit discriminants are used with fieldless enums in the normal C-like style.
Is using constants instead of enums okay?
Yes, if you only need named values. But if the values represent a closed set of choices, enums are usually safer and clearer.
Can I convert an enum variant to its discriminant value?
For fieldless enums, you can often cast with as, such as MyEnum::A as i32. In lower-level code, use #[repr(...)] when representation matters.
Mini Project
Description
Build a small Rust program that models command types for a fictional device. Each command should be represented as an enum variant, and each variant should map to a fixed numeric code. This demonstrates the common Rust pattern of using a fieldless enum with a method instead of trying to put literal values inside tuple-like enum variant definitions.
Goal
Create an enum whose variants represent device commands and provide a method that returns the fixed numeric code for each command.
Requirements
- Define a fieldless enum named
Commandwith at least three variants. - Add a method that returns a numeric code for each variant.
- In
main, create a few enum values and print their codes. - Use
matchto map variants to constants. - Keep the program valid and runnable as a single Rust file.
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.