10.2 Basic Enums in Rust and C
Let's start by comparing how basic enums are used in Rust and C.
10.2.1 Rust Example: Simple Enum
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"), } }
- Definition: The
Direction
enum lists four possible variants. - Usage: We create a variable
heading
with the valueDirection::North
. - Pattern Matching: The
match
expression handles each possible variant.
10.2.2 Assigning Integer Values to Enums
In Rust, you can assign specific integer values to enum variants, similar to C. This can be useful when interfacing with C code or when specific integer values are needed.
Example:
#[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)]
: Specifies the underlying representation asi32
.- Assigning Values: Variants are assigned specific integer values, including negative numbers.
- Casting: You can cast the enum variant to its underlying integer type using the
as
keyword.
Notes:
- Custom Values: You can assign any integer values to enum variants, including negative values and non-sequential numbers, creating gaps.
- Underlying Types: You can specify types like
u8
,i32
, etc., as the underlying type using the#[repr]
attribute.
Casting from Integers to Enums
While you can cast enum variants to their underlying integer type, casting in the opposite direction (from integers to enums) is unsafe and requires explicit handling.
Example:
#[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); }
- Unsafe Casting: Using
std::mem::transmute
is unsafe because it can lead to invalid enum values if the integer doesn't correspond to a valid variant. - Recommendation: Avoid casting from integers to enums unless you can guarantee the integer represents a valid variant.
10.2.3 Using Enums for Array Indexing
While Rust enums with assigned integer values can be cast to integers, using them directly for array indexing requires caution.
Example:
#[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 to
usize
: The enum variant is cast tousize
for indexing. - Safety Considerations: Ensure that the enum values correspond to valid indices.
Warning: Using enums for array indexing can be unsafe if there are gaps or negative values. Always validate or constrain the enum variants when using them for indexing.
10.2.4 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: The
Direction
enum assigns names to integer constants starting from 0. - Usage: We declare a variable
heading
of typeenum Direction
. - Switch Statement: Similar to Rust's
match
, we use aswitch
statement to handle each case.
10.2.5 Advantages of Rust's Enums
While both examples are similar, Rust's enums provide additional safety:
- No Implicit Conversion: In Rust, you cannot implicitly convert between integers and enum variants, preventing accidental misuse.
- Exhaustiveness Checking: Rust's
match
expressions require handling all possible variants unless you use a wildcard_
, reducing the chance of missing cases. - Type Safety: Enums are distinct types, not just integers, enhancing type safety.