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 value Direction::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 as i32.
  • 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 to usize 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 type enum Direction.
  • Switch Statement: Similar to Rust's match, we use a switch 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.