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
| Feature | Enum | Struct |
|---|---|---|
| Purpose | One of several variants | Group related fields |
| Variants | Multiple named options | Single structure |
| Data | Each variant can differ | All fields present |
| Matching | Pattern match on variant | Access fields directly |
| Use Case | States, choices, errors | Data 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