Structs, Enums, and Modules

Explore struct and enum definitions for creating custom data structures. Learn how to organize code using modules.


Enums in Rust: Defining Custom Data Types

In Rust, enums (short for enumerations) are a powerful way to define a type that can be one of several possible variants. They allow you to create expressive and type-safe code by representing a fixed set of options.

What are Enums?

An enum defines a new type where each possible value is named a variant. Enums are useful when you have a variable that can only take on a limited number of known values. They are more descriptive and less error-prone than using integers or strings to represent these choices.

Defining Enums

Here's a basic example of an enum definition:

 enum WebEvent {
    PageLoad,
    PageUnload,
    KeyPress(char),
    Paste(String),
    Click { x: i64, y: i64 },
} 

In this example, WebEvent is an enum with five variants:

  • PageLoad and PageUnload are simple variants without any associated data.
  • KeyPress is a variant that holds a single char value.
  • Paste is a variant that holds a String.
  • Click is a variant that holds a struct-like definition with named fields x and y of type i64.

Enums With and Without Data

As shown in the example above, enum variants can optionally hold data. This makes enums incredibly versatile. They can represent simple options or complex data structures.

Enums Without Data (Unit Variants)

Unit variants, like PageLoad and PageUnload, are similar to constants. They represent a specific state or option without any additional information.

 enum Status {
    Online,
    Offline,
    Idle,
} 

Enums With Data

Variants can hold data of any type. This data can be a single value, a tuple of values, or even a struct-like definition with named fields.

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

The Result enum (which is part of Rust's standard library) is a prime example. It represents the outcome of an operation that might succeed or fail. Ok holds the successful result (of type T), and Err holds the error value (of type E). This is a generic enum allowing it to return success and error types of different types.

Using Enums

To use an enum, you refer to its variants using the enum's name followed by the scope resolution operator (::) and the variant name.

 let load = WebEvent::PageLoad;
let key = WebEvent::KeyPress('x');
let paste = WebEvent::Paste("some text".to_string());
let click = WebEvent::Click { x: 10, y: 20 }; 

Pattern Matching with Enums

One of the most powerful features of enums is their integration with pattern matching using the match keyword. Pattern matching allows you to execute different code blocks depending on which variant of the enum is present.

 fn process_event(event: WebEvent) {
    match event {
        WebEvent::PageLoad => println!("Page loaded!"),
        WebEvent::PageUnload => println!("Page unloaded!"),
        WebEvent::KeyPress(character) => println!("Key pressed: {}", character),
        WebEvent::Paste(text) => println!("Pasted text: {}", text),
        WebEvent::Click { x, y } => println!("Clicked at x={}, y={}", x, y),
    }
}

fn main() {
    let my_event = WebEvent::Click { x: 50, y: 100 };
    process_event(my_event);
} 

In this example, the process_event function takes a WebEvent as input. The match statement checks which variant the event is. For variants that hold data (KeyPress, Paste, Click), you can bind the data to variables (e.g., character, text, x, y) and use them in the corresponding code block.

The match expression must be exhaustive, meaning it must handle all possible variants of the enum. If you don't want to handle all variants explicitly, you can use the underscore (_) as a catch-all pattern.

 match some_result {
  Ok(value) => println!("Success: {}", value),
  Err(_) => println!("An error occurred!"), // Ignoring the specific error
} 

Conclusion

Enums are a fundamental part of Rust programming. They provide a way to create type-safe and expressive code by defining custom data types that can hold one of several possible values. The combination of enums and pattern matching allows you to write elegant and robust code that handles different scenarios effectively. Use enums whenever you have a variable that can only take on a limited number of known values.