ProgrammingMiddle Rust Developer

What is the principle of the most effective use of enum in Rust for type-safe modeling of state and errors, and what pattern matching nuances should be considered?

Pass interviews with Hintsage AI assistant

Answer.

Enums in Rust are fundamentally different from enums in C/C++: they can store associated data and are perfect for modeling state and errors. They are used to build type-safe finite-state machines, various forms of Option/Result, and implement the "sum types" pattern. Historically, similar constructs have been used in functional languages to describe the variations of an entity with strictly delineated options.

Problem: To achieve expressiveness (to express all state options), where each case handling is required, and it's impossible to accidentally skip a branch. Project-wide errors can be difficult to type without such an expressive structure.

Solution: Enums with associated data and pattern matching provide control — each branch is checked by the compiler, ensuring exhaustiveness. Additionally, a plethora of utility methods have already been implemented for Result and Option.

Code example:

enum NetworkState { Disconnected, Connecting(u32), // attempt number Connected(String), Error(String), } fn print_state(state: NetworkState) { match state { NetworkState::Disconnected => println!("Net: disconnected"), NetworkState::Connecting(count) => println!("Net: connecting (attempt {})", count), NetworkState::Connected(addr) => println!("Net: connected to {}", addr), NetworkState::Error(msg) => println!("Net error: {}", msg), } }

Key features:

  • Enum can have different variants with their own data types
  • Pattern matching ensures handling of all variants (or warns about missed ones)
  • Allows expressing errors without exceptions, with type safety

Trick questions.

Can enum branches be partially handled without _?

The compiler prohibits unclosed cases for non-exhaustive enums, but if _ is used, unhandled branches will be "absorbed". One should avoid _ in clinically important branches to ensure future changes are not overlooked.

In what cases are associated values referenced, and in which are they copied during pattern matching?

During pattern matching, associated data is moved by default. If only viewing is needed, use references:

match &state { NetworkState::Connected(addr) => println!("by ref: {}", addr), _ => {} }

Can two enums with overlapping variant names be used in one structure?

Yes, but the variant names are used with the enum prefix. This avoids collisions and makes the code self-documenting (e.g., Status::Ok vs NetworkState::Ok).

Common mistakes and anti-patterns

  • Using _ to hide newer options when extending enums
  • Moving significant data from enums accidentally during pattern matching
  • Overusing catch-all (e.g., _ =>) in critical error handlers

Real-life examples

Negative case

In the code, processing Result<T, E> consistently has a catch-all via _ =>, and new errors (when extending enums) go unnoticed — leading to silent loss of errors.

Pros:

  • Conciseness of code, minimal boilerplate

Cons:

  • Loss of control over execution flow, critical failures remain unaccounted

Positive case

Exhaustiveness matching is used, each Enum variant is explicitly handled, or a panic occurs in a branch for which there is no stable behavior.

Pros:

  • Clarity of logic and transparent maintainability
  • When adding a new state, the compiler will warn in time

Cons:

  • Sometimes longer code, requiring explicit handling of all options