Enums - Feature 19/41

Enums (enumerations) define types with a fixed set of named variants. They're perfect for representing choices, states, and data that can be one of several options.

Defining Enums

enum Status {
  Pending,
  Active,
  Completed,
  Cancelled
}

enum Direction {
  North,
  South,
  East,
  West
}

Test Coverage: ✅ tests/lang_comp/data_structures/enums.rs

Try It in the Notebook

enum Color {
  Red,
  Green,
  Blue
}

let color = Color::Red
color  // Returns: Color::Red

Expected Output: Color::Red

Using Enum Variants

Access variants with :: notation:

enum TrafficLight {
  Red,
  Yellow,
  Green
}

let light = TrafficLight::Red

Expected Output: TrafficLight::Red

Pattern Matching with Enums

enum Status {
  Pending,
  Active,
  Completed
}

fn describe_status(status) {
  match status {
    Status::Pending => "Not started yet",
    Status::Active => "Currently working",
    Status::Completed => "All done!"
  }
}

describe_status(Status::Active)  // Returns: "Currently working"

Expected Output: "Currently working"

Enums with Data

Variants can hold data:

enum Message {
  Quit,
  Move { x: i32, y: i32 },
  Write(String),
  ChangeColor(i32, i32, i32)
}

let msg1 = Message::Quit
let msg2 = Message::Move { x: 10, y: 20 }
let msg3 = Message::Write("Hello")
let msg4 = Message::ChangeColor(255, 0, 0)

Expected Output: Various message types with data

Pattern Matching with Data

enum Message {
  Quit,
  Move { x: i32, y: i32 },
  Write(String)
}

fn process(msg) {
  match msg {
    Message::Quit => "Quitting",
    Message::Move { x, y } => f"Moving to ({x}, {y})",
    Message::Write(text) => f"Writing: {text}"
  }
}

process(Message::Move { x: 10, y: 20 })  // Returns: "Moving to (10, 20)"

Expected Output: "Moving to (10, 20)"

Option Type

Built-in enum for optional values:

enum Option<T> {
  Some(T),
  None
}

fn find(arr, target) {
  for item in arr {
    if item == target {
      return Some(item)
    }
  }
  None
}

let result = find([1, 2, 3], 2)
match result {
  Some(value) => f"Found: {value}",
  None => "Not found"
}
// Returns: "Found: 2"

Expected Output: "Found: 2"

Option Methods

let some_value = Some(42)
let no_value = None

some_value.is_some()  // Returns: true
some_value.is_none()  // Returns: false
no_value.is_some()    // Returns: false
no_value.is_none()    // Returns: true

Expected Output: true, false, false, true

Unwrapping Option

let value = Some(42)

value.unwrap()           // Returns: 42
value.unwrap_or(0)       // Returns: 42
value.unwrap_or_else(|| 0)  // Returns: 42

let none = None
none.unwrap_or(0)        // Returns: 0

Expected Output: 42, 42, 42, 0

Result Type

Built-in enum for operations that can fail:

enum Result<T, E> {
  Ok(T),
  Err(E)
}

fn divide(a, b) {
  if b == 0 {
    Err("Division by zero")
  } else {
    Ok(a / b)
  }
}

let result = divide(10, 2)
match result {
  Ok(value) => f"Result: {value}",
  Err(error) => f"Error: {error}"
}
// Returns: "Result: 5"

Expected Output: "Result: 5"

Result Methods

let success = Ok(42)
let failure = Err("error")

success.is_ok()   // Returns: true
success.is_err()  // Returns: false
failure.is_ok()   // Returns: false
failure.is_err()  // Returns: true

Expected Output: true, false, false, true

Common Patterns

State Machine

enum State {
  Idle,
  Running,
  Paused,
  Stopped
}

fn transition(state, event) {
  match (state, event) {
    (State::Idle, "start") => State::Running,
    (State::Running, "pause") => State::Paused,
    (State::Paused, "resume") => State::Running,
    (State::Running, "stop") => State::Stopped,
    (State::Paused, "stop") => State::Stopped,
    _ => state  // No transition
  }
}

transition(State::Idle, "start")  // Returns: State::Running

Expected Output: State::Running

HTTP Status

enum HttpStatus {
  Ok,
  Created,
  BadRequest,
  Unauthorized,
  NotFound,
  InternalServerError
}

fn status_code(status) {
  match status {
    HttpStatus::Ok => 200,
    HttpStatus::Created => 201,
    HttpStatus::BadRequest => 400,
    HttpStatus::Unauthorized => 401,
    HttpStatus::NotFound => 404,
    HttpStatus::InternalServerError => 500
  }
}

status_code(HttpStatus::NotFound)  // Returns: 404

Expected Output: 404

JSON Value

enum JsonValue {
  Null,
  Bool(bool),
  Number(f64),
  String(String),
  Array(Vec<JsonValue>),
  Object(HashMap<String, JsonValue>)
}

let data = JsonValue::Object({
  "name": JsonValue::String("Alice"),
  "age": JsonValue::Number(30),
  "active": JsonValue::Bool(true)
})

Expected Output: Object with structured JSON data

Command Pattern

enum Command {
  Create { name: String },
  Update { id: i32, name: String },
  Delete { id: i32 },
  List
}

fn execute(cmd) {
  match cmd {
    Command::Create { name } => f"Creating {name}",
    Command::Update { id, name } => f"Updating {id} to {name}",
    Command::Delete { id } => f"Deleting {id}",
    Command::List => "Listing all items"
  }
}

execute(Command::Create { name: "Item" })  // Returns: "Creating Item"

Expected Output: "Creating Item"

Enum Methods

Define methods on enums with impl:

enum Status {
  Pending,
  Active,
  Completed
}

impl Status {
  fn is_done(&self) -> bool {
    match self {
      Status::Completed => true,
      _ => false
    }
  }

  fn message(&self) -> String {
    match self {
      Status::Pending => "Waiting to start",
      Status::Active => "In progress",
      Status::Completed => "Finished"
    }
  }
}

let status = Status::Active
status.is_done()   // Returns: false
status.message()   // Returns: "In progress"

Expected Output: false, "In progress"

Recursive Enums

Enums can be recursive (with Box):

enum List {
  Cons(i32, Box<List>),
  Nil
}

let list = List::Cons(1, Box::new(
  List::Cons(2, Box::new(
    List::Cons(3, Box::new(List::Nil))
  ))
))

Expected Output: Linked list: 1 -> 2 -> 3 -> Nil

Enum Comparison

enum Color {
  Red,
  Green,
  Blue
}

Color::Red == Color::Red    // Returns: true
Color::Red == Color::Blue   // Returns: false

Expected Output: true, false

Generic Enums

enum Container<T> {
  Empty,
  Single(T),
  Multiple(Vec<T>)
}

let int_container = Container::Single(42)
let str_container = Container::Multiple(["a", "b", "c"])

Expected Output: Containers with different types

Best Practices

✅ Use Enums for Fixed Choices

// Good: Clear, type-safe
enum PaymentMethod {
  CreditCard,
  DebitCard,
  PayPal,
  BankTransfer
}

// Bad: String magic values
let payment = "credit_card"  // Typos, no validation

✅ Prefer Pattern Matching

// Good: Exhaustive, compiler-checked
match status {
  Status::Pending => handle_pending(),
  Status::Active => handle_active(),
  Status::Completed => handle_completed()
}

// Bad: Multiple if-else
if status == Status::Pending {
  handle_pending()
} else if status == Status::Active {
  handle_active()
} else {
  handle_completed()
}

✅ Use Option Instead of Null

// Good: Type-safe, forces handling
fn find_user(id: i32) -> Option<User> {
  // ...
}

match find_user(123) {
  Some(user) => use_user(user),
  None => handle_not_found()
}

// Bad: Null values, runtime errors
fn find_user(id: i32) -> User {
  // Returns null if not found - crashes!
}

✅ Use Result for Error Handling

// Good: Explicit error handling
fn parse_int(s: String) -> Result<i32, String> {
  // Returns Ok(value) or Err(message)
}

// Bad: Magic error values
fn parse_int(s: String) -> i32 {
  // Returns -1 on error? 0? Ambiguous!
}

Enums vs Structs

FeatureEnumStruct
PurposeOne of several variantsGroup related fields
VariantsMultiple named optionsSingle structure
DataEach variant can differAll fields present
MatchingPattern match on variantAccess fields directly
Use CaseStates, choices, errorsData models, entities
// Enum: Represents one of several options
enum Shape {
  Circle { radius: f64 },
  Rectangle { width: f64, height: f64 }
}

// Struct: Represents a single entity
struct Point {
  x: f64,
  y: f64
}

Summary

Feature Status: WORKING ✅ Test Coverage: 100% ✅ Mutation Score: 98%

Enums define types with fixed sets of variants, enabling type-safe state machines, error handling, and optional values. They're fundamental to Ruchy's type system.

Key Takeaways:

  • Define variants with enum Name { Variant1, Variant2 }
  • Access with Name::Variant
  • Pattern match with match
  • Built-in: Option<T> (Some/None), Result<T, E> (Ok/Err)
  • Variants can hold data
  • Better than magic values or null

← Previous: Structs | Next: Pattern Matching →