Question

How to Create Enums With Constant Values in Rust

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:

  • A can store any i32 value
  • B can store any i32 value

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:

  1. Plain enum variants with no data
  2. Enum discriminants for integer-backed values
  3. Methods or match expressions to map variants to constants
  4. 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 A corresponds to 123
  • variant B corresponds to 456

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: the A card has a pocket that can hold any i32
  • A by itself means: the A card 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:

  • A maps to 123
  • B maps to 456

So the enum is the name, and a method or match is the legend that tells you what each name means.

Take Quiz

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

  1. enum MyEnum { A, B }

    • Defines two possible variants.
    • Neither variant stores extra data.
  2. impl MyEnum { fn value(self) -> i32 { ... } }

    • Adds a method named value.
    • The method returns an i32 based on the variant.
  3. let item = MyEnum::B;

    • Creates a value of type MyEnum.
    • Its variant is B.
  4. let number = item.value();

    • Calls the method.
    • Inside the match, Rust checks which variant item is.
    • Since it is MyEnum::B, the method returns 456.
  5. println!("{}", number);

    • Prints 456.

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.

Take Quiz

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 like 123.

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

PatternExampleBest whenNotes
Enum with payloadA(i32)each variant carries variable dataA(1) and A(999) are both valid
Fieldless enum + methodA, B + value()each variant maps to a fixed meaningmost idiomatic for fixed per-variant values
Fieldless enum + discriminantsA = 123you need explicit numeric codesuseful for casting and interop
Associated constantsconst A: i32 = 123you only need named constantsno enum behavior or pattern matching

Fieldless enum vs tuple enum

enum WithData {
    A(i32),
}

enum WithoutData {
    A,
}
  • WithData::A(5) stores data
  • WithoutData::A is only a variant name

Method mapping vs discriminant

ApproachExampleAdvantageDrawback
Method mappingfn value() -> i32flexible, readable, can compute valuesrequires a match
DiscriminantA = 123direct numeric identityonly 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.

Take Quiz

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) means A carries a value of type i32
  • A(123) is invalid in an enum definition because 123 is a value, not a type
  • Use A = 123 only 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 const or 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 an i32
  • A = 123 means the variant has discriminant value 123

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.

Take Quiz

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 Command with 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 match to map variants to constants.
  • Keep the program valid and runnable as a single Rust file.
Take Quiz

Keep learning

Related questions

View more