AI Assistance Disclosure: AI assistance was used to help draft and proofread the English text, as I'm not a native English speaker. ![]()
1. Summary
I'd like to explore an extension to Rust enums that would let us declare common, typed properties directly in the enum definition, with predefined values for each variant.
Consider a regular Rust enum:
enum Color {
Red,
Green,
Blue,
}
Each variant is a predefined value that can be used without passing any arguments.
The same idea could work for enums with associated properties:
// Proposed syntax
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Status(
pub code: u16,
pub label: &'static str,
retryable: bool,
) {
Pending(0, "Pending", true),
Running(1, "In progress", true),
Completed(2, "Completed", false),
}
Usage:
let status = Status::Running;
assert_eq!(status.code(), 1);
assert_eq!(status.label(), "In progress");
In both cases, each variant is a predefined value. The second form also defines property values for each variant.
Another part of this proposal is to add optional built-in FromStr and Display derives, making it easier to convert between enum variants and their names.
All the proposed syntax and derives shown here are hypothetical and are not currently supported by Rust.
2. Motivation
Having worked extensively with Java for over ten years, I've always appreciated its approach to enums, particularly the ability to define predefined constants with associated properties and methods in a concise and elegant way.
Rust has a powerful enum system, but I feel that a simpler way to express this particular pattern is missing.
I generally prefer enums over strings when dealing with a finite set of predefined values. In my experience, this is particularly useful when working with APIs, where enums can represent statuses, operation types, error categories, and other values exchanged in requests and responses.
This isn't limited to APIs. There are many situations where we want a fixed set of variants, each with its own predefined properties. In Rust, handling these properties often means writing repetitive match expressions or relying on external crates.
I've had this idea in mind for quite some time, and I'd like to explore whether Rust could offer a more natural way to express it while preserving Rust's existing enum capabilities and conventions.
Reducing repetitive property mappings
In Rust today, a common way to associate several properties with enum variants is to write separate methods using match:
enum Status {
Pending,
Running,
Completed,
}
impl Status {
fn code(&self) -> u16 {
match self {
Self::Pending => 0,
Self::Running => 1,
Self::Completed => 2,
}
}
fn label(&self) -> &'static str {
match self {
Self::Pending => "Pending",
Self::Running => "In progress",
Self::Completed => "Completed",
}
}
fn retryable(&self) -> bool {
match self {
Self::Pending => true,
Self::Running => true,
Self::Completed => false,
}
}
}
With the proposed syntax, we could write the same thing directly in the enum definition:
// Proposed syntax
enum Status(
code: u16,
label: &'static str,
retryable: bool,
) {
Pending(0, "Pending", true),
Running(1, "In progress", true),
Completed(2, "Completed", false),
}
I see several benefits:
- Everything in one place: each variant and its properties are defined together.
- Less boilerplate: no need to maintain separate
matchexpressions for each property. - Type safety: property values must match their declared types.
- Easier maintenance: adding or changing variants and properties requires fewer changes.
- Stable accessors: existing accessor calls can remain unchanged when new properties are added.
The more variants and properties an enum has, the more useful this becomes.
3. Proposed Behavior
3.1. Predefined variants
The proposal covers two forms of enums.
Existing unit-only enums:
enum Color {
Red,
Green,
Blue,
}
Enums with predefined properties:
// Proposed syntax
enum Priority(
pub level: u8,
pub label: &'static str,
) {
Low(1, "Low"),
Medium(2, "Medium"),
High(3, "High"),
}
Both represent a finite set of predefined values.
For example:
let color = Color::Red;
let priority = Priority::High;
Neither requires additional arguments when selecting a variant.
With the second form, each variant also provides values for the properties declared in the enum definition.
3.2. Properties and visibility
Properties could use primitive types, references, or user-defined types.
Like other Rust members, they would be private by default, with pub used when public access is needed.
For example:
// Proposed syntax
pub enum Status(
pub code: u16,
pub label: &'static str,
retryable: bool,
) {
Pending(0, "Pending", true),
Running(1, "In progress", true),
Completed(2, "Completed", false),
}
Each property would have a generated read-only accessor:
status.code()
status.label()
The accessors would follow the visibility of their properties.
In this example, code() and label() would be public, while retryable() would remain private.
Private properties could still be used by methods with the appropriate access:
impl Status {
pub fn is_retryable(&self) -> bool {
self.retryable()
}
pub fn description(&self) -> String {
format!("{} ({})", self.label(), self.code())
}
}
No setters would be generated, and the associated property values could not be reassigned.
Accessors would follow Rust's normal ownership and borrowing rules, returning references where appropriate.
Generated property accessors should be usable in const contexts whenever their associated values and operations satisfy Rust's existing const-evaluation rules.
3.3. Custom types and associated objects
Properties wouldn't be limited to primitive types.
For example:
// Proposed syntax
enum Profile(
timeout: Timeout,
delay: Delay,
) {
Fast(Timeout::short(), Delay::short()),
Conservative(Timeout::long(), Delay::long()),
}
Here, Timeout and Delay are two different user-defined types. Each variant would have to provide values of the correct types.
It should also be possible to associate more complex objects with variants:
// Proposed syntax
enum Service(config: Config) {
Primary(Config::new("primary")),
Secondary(Config::new("secondary")),
}
Each variant would have its own predefined configuration.
The configuration should be initialized only once, either on first access or through an appropriate earlier initialization mechanism. It should not be reconstructed every time the property is accessed.
For example, repeated accesses to Service::Primary.config() should refer to the same associated configuration object.
The association itself would remain fixed, although the object's internal state could still change through Rust's usual mutability mechanisms.
3.4. Variant identity and standard traits
Variants should remain distinct even when some or all of their properties have identical values.
For example:
// Proposed syntax
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum Status(code: u16) {
Pending(1),
Running(1),
}
Both variants have the same code, but they are still different variants:
assert_ne!(Status::Pending, Status::Running);
For derived traits, I'd expect the following behavior:
| Trait | Behavior |
|---|---|
Copy |
Copies the enum value without duplicating associated objects |
Clone |
Produces the same enum variant without reconstructing its properties |
PartialEq / Eq |
Compares variant identity |
Hash |
Hashes variant identity |
Ord / PartialOrd |
Uses variant ordering when derived |
These traits would remain opt-in through derive, and developers could still provide custom implementations.
3.5. Methods and pattern matching
Regular impl blocks should continue to work as usual.
For example:
// Proposed syntax
enum Operation(
pub symbol: &'static str,
precedence: u8,
right_associative: bool,
) {
Add("+", 10, false),
Subtract("-", 10, false),
Multiply("*", 20, false),
Divide("/", 20, false),
Power("^", 30, true),
}
impl Operation {
pub fn binds_tighter_than(&self, other: &Self) -> bool {
self.precedence() > other.precedence()
}
pub fn is_right_associative(&self) -> bool {
self.right_associative()
}
}
Usage:
let a = Operation::Multiply;
let b = Operation::Add;
assert!(a.binds_tighter_than(&b));
Methods could use the associated properties directly, without needing separate match expressions to retrieve each property.
Normal pattern matching would still work:
match a {
Operation::Add => handle_add(),
Operation::Subtract => handle_subtract(),
Operation::Multiply => handle_multiply(),
Operation::Divide => handle_divide(),
Operation::Power => handle_power(),
}
We wouldn't need to destructure the associated properties just to identify a variant.
4. Optional FromStr and Display Derivations
Another possibility is to add optional built-in derives for converting predefined enum variants to and from their names.
Rust already has the standard FromStr and Display traits, but it doesn't provide built-in derives for them.
This could be especially useful when working with external systems, including APIs that exchange predefined values as strings. Converting between strings and typed enum variants would require less repetitive code, while still allowing applications to use strongly typed values internally.
4.1. Unit-only enums
For a regular enum:
// Proposed built-in derives
#[derive(Debug, PartialEq, Eq, FromStr, Display)]
enum Color {
Red,
Green,
Blue,
}
We could write:
let color: Color = "Green".parse()?;
assert_eq!(color, Color::Green);
assert_eq!(color.to_string(), "Green");
assert!("Unknown".parse::<Color>().is_err());
The conversion would simply use the variant name.
4.2. Enums with predefined properties
The same derives should also work with the proposed syntax:
// Proposed syntax and built-in derives
#[derive(Debug, PartialEq, Eq, FromStr, Display)]
enum Status(
pub code: u16,
pub label: &'static str,
) {
Pending(0, "Waiting"),
Running(1, "In progress"),
Completed(2, "Finished"),
}
Usage:
let status: Status = "Running".parse()?;
assert_eq!(status, Status::Running);
assert_eq!(status.to_string(), "Running");
assert_eq!(status.label(), "In progress");
assert_eq!(status.code(), 1);
The conversion would use the variant name, not its associated properties:
"Running"is the variant name."In progress"is its associated label.1is its associated numeric code.
4.3. Conversion behavior
The default behavior I'm proposing is:
FromStrwould recognize exact, case-sensitive variant names.- Unknown names would return an error.
Displaywould output the variant name.- Both derives would be optional and independent of each other.
- Developers could still write their own trait implementations.
These derives would apply to enums whose variants are already complete, predefined values, including regular unit-only enums and the proposed property-bearing enums.
They wouldn't automatically apply to variants that require runtime construction arguments.
Since name-based conversions are useful even without associated properties, this part could be developed together with the enum extension or separately.
5. Existing Approaches
There are already several crates that address similar needs.
table_enum provides a declarative way to define enum variants with associated properties and generated accessor methods.
I came up with this idea independently and only discovered table_enum later, while looking into existing approaches.
const_table offers another way to associate data with enum variants.
Strum provides enum-related utilities, including EnumString for implementing FromStr and a Display derive macro.
These libraries show that the pattern is already useful. However, they rely on external macros and their own syntax. It would be interesting to explore whether Rust could support this directly in its enum declarations, with consistent behavior and tooling support.
6. Conclusion
The idea is to make it easier to define enums with a fixed set of variants, where each variant can also have predefined values for common, typed properties.
Rust's existing unit-only enums, such as Color { Red, Green, Blue }, already represent predefined variants. I'd like to extend that idea to properties, without losing the things that make Rust enums useful, such as variant identity, traits, methods, and pattern matching.
This would avoid writing repetitive property mappings and keep related values together in one place.
I'd also like to see optional built-in FromStr and Display derives for converting predefined variants to and from their names, whether or not they have associated properties.
I'd be interested to hear whether others have encountered similar needs, whether this pattern would benefit from native language support, and what they think of the proposed syntax.