10.2 Basic Enums in Rust and C
The simplest form of an enum in Rust closely resembles a C enum: a set of named variants without associated data.
10.2.1 Rust Example: Simple Enum
A simple Rust enum is similar to a C enum in that it defines a type with a fixed set of named variants.
Here is a complete example demonstrating how to use the enum and a match expression:
enum Direction { North, East, South, West, } fn main() { let heading = Direction::North; match heading { Direction::North => println!("Heading North"), Direction::East => println!("Heading East"), Direction::South => println!("Heading South"), Direction::West => println!("Heading West"), } }
In Rust, each variant of an enum is namespaced by the enum type itself, using the :: notation.
Here, Direction is the enum type, with four possible variants: North, East, South, and West. Each of these variants represents a distinct state.
To use an enum, you must specify both the enum type and variant, separated by ::. This prevents naming conflicts, as the same variant name can exist in multiple enums without ambiguity.
The match construct is a powerful pattern-matching mechanism in Rust. It checks the value of heading and runs different blocks of code depending on which variant is matched. A key requirement of Rust’s match expression is exhaustiveness: all possible variants must be handled.
When run, this code prints “Heading North” because heading is set to Direction::North. The match expression explicitly covers each variant of Direction, ensuring that the program remains robust and readable.
- Definition:
Directionhas four variants. - Usage: You can assign
Direction::Northtoheading. - Pattern Matching: The
matchexpression requires handling all variants.
10.2.2 Comparison with C: Simple Enum
#include <stdio.h>
enum Direction {
North,
East,
South,
West,
};
int main() {
enum Direction heading = North;
switch (heading) {
case North:
printf("Heading North\n");
break;
case East:
printf("Heading East\n");
break;
case South:
printf("Heading South\n");
break;
case West:
printf("Heading West\n");
break;
default:
printf("Unknown heading\n");
}
return 0;
}
- Definition: Each variant is an integer constant starting from 0.
- Usage: Declares
headingof typeenum Direction. - Switch Statement: Similar in concept to Rust’s
matchexpression.
10.2.3 Assigning Integer Values to Enums
Optionally, you can assign integer values to Rust enum variants, which can be especially useful for interfacing with C or whenever numeric representations are needed:
#[repr(i32)] enum ErrorCode { NotFound = -1, PermissionDenied = -2, ConnectionFailed = -3, } fn main() { let error = ErrorCode::NotFound; let error_value = error as i32; println!("Error code: {}", error_value); }
#[repr(i32)]: Specifiesi32as the underlying type.- Value Assignments: Variants can have any integer values, including negatives or gaps.
- Casting: Convert to the integer representation with the
askeyword.
Casting from Integers to Enums
Reversing the cast—from an integer to an enum—can be risky:
#[repr(u8)] enum Color { Red = 0, Green = 1, Blue = 2, } fn main() { let value: u8 = 1; let color = unsafe { std::mem::transmute::<u8, Color>(value) }; println!("Color: {:?}", color); }
transmute: Unsafe because the integer might not correspond to a valid enum variant.- Best Practice: Avoid direct integer-to-enum casts unless you can guarantee valid values.
10.2.4 Using Enums for Array Indexing
When you assign numeric values to variants, you can use them as array indices—just be careful:
#[repr(u8)] enum Color { Red = 0, Green = 1, Blue = 2, } fn main() { let palette = ["Red", "Green", "Blue"]; let color = Color::Green; let index = color as usize; println!("Selected color: {}", palette[index]); }
- Casting: Convert
Colortousizebefore indexing. - Safety: Ensure every variant corresponds to a valid index.
10.2.5 Advantages of Rust’s Simple Enums
Compared to C, Rust provides:
- No Implicit Conversion: No silent mixing of enums and integers.
- Exhaustiveness: Rust requires handling all variants in a
match. - Stronger Type Safety: Enums are first-class types rather than integer constants.