10.8 Common Use Cases

A key strength of Rust enums is their ability to unify different kinds of data under a single type. Even though variants like Message::Quit and Message::Write(String) represent conceptually different information and may contain data of different types and sizes, they both belong to the same Message enum type. Furthermore, as discussed in Section 10.5, all instances of an enum have the same, fixed size in memory.

This uniformity in type and size allows enums to represent conceptually heterogeneous data in contexts where Rust’s static typing requires a single, consistent type. This makes them invaluable for scenarios like:

  1. Storing different kinds of related information within the same collection (e.g., Vec, HashMap).
  2. Enabling functions to accept arguments or return values that could represent one of several distinct possibilities or states.

10.8.1 Storing Enums in Collections

Because all variants of an enum share the same type (Message in our example) and have a consistent size, they work seamlessly in collections designed for homogeneous elements, like Vec.

// Hidden setup code
#[derive(Debug)]
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(u8, u8, u8),
}
// Minimal impl needed for example
impl Message { fn describe(&self) -> String { format!("{:?}", self) } }

fn main() {
// This Vec holds elements of type Message.
let mut messages: Vec<Message> = Vec::new();

// We can push different variants into the same Vec.
messages.push(Message::Quit);
messages.push(Message::Move { x: 10, y: 20 });
messages.push(Message::Write(String::from("Enum in a Vec")));

println!("Processing messages stored in a Vec:");
for msg in &messages { // Iterate over references (&Message)
    // We use pattern matching to handle the specific variant of each element.
    match msg {
        Message::Write(text) => println!("  Found Write: {}", text),
        Message::Quit => println!("  Found Quit"),
        _ => println!("  Found other message: {}", msg.describe()),
    }
}
}
  • Homogeneous Collection Type: The Vec<Message> itself is homogeneous, storing only Message types.
  • Heterogeneous Conceptual Data: The values stored within the Vec can represent different kinds of messages (Quit, Move, Write).
  • Consistent Size: Allows efficient, contiguous storage within the Vec.

10.8.2 Passing Enums to Functions

Similarly, functions can accept or return a single enum type, allowing them to operate on or produce values that represent one of several possibilities.

#[derive(Debug)]
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(u8, u8, u8),
}

// This function accepts any Message variant by value (taking ownership).
// It returns a String, demonstrating using match inside the function.
fn handle_message(msg: Message) -> String {
    let status_prefix = "Status: ";
    match msg {
        Message::Quit => format!("{}Quitting", status_prefix),
        Message::Move { x, y } => format!("{}Moved to ({}, {})", status_prefix, x, y),
        // 'msg' is owned, so we can take ownership of 'text' directly here.
        Message::Write(text) => format!("{}Wrote '{}'", status_prefix, text),
        Message::ChangeColor(r, g, b) =>
            format!("{}Color changed ({},{},{})", status_prefix, r, g, b),
    }
}

// Example function that might return different variants
fn check_input(input: &str) -> Result<i32, Message> {
    if input == "quit" {
        Err(Message::Quit) // Return an Err variant of Result containing a Message::Quit
    } else if let Ok(num) = input.parse::<i32>() {
        Ok(num) // Return an Ok variant containing the parsed number
    } else {
        // Return an Err variant containing a Message::Write
        Err(Message::Write(format!("Invalid input: {}", input)))
    }
}

fn main() {
    let my_message = Message::ChangeColor(0, 255, 0);
    let status = handle_message(my_message); // my_message is moved here
    println!("{}", status);

    println!("\nChecking inputs:");
    let inputs = ["123", "hello", "quit"];
    for input in inputs {
        match check_input(input) {
            Ok(num) =>
                println!("  Input '{}': Parsed number {}", input, num),
            Err(Message::Quit) =>
                println!("  Input '{}': Quit signal received", input),
            Err(Message::Write(err_text)) =>
                println!("  Input '{}': Error - {}", input, err_text),
            Err(other_msg) =>
                println!("  Input '{}': Unexpected error variant {:?}",
                input, other_msg),
        }
    }
}