Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 14

Rust Enums: Defining Age Stages

Recommended resources

Hello! Welcome back to our project.

In the last lesson, we laid the structural groundwork for our characters in Rust by creating the main Character struct and a nested Personality struct. We now have a solid representation for a character's name, their HEXACO personality profile, and their traits.

However, you might recall our Character struct included this line:
pub age_stage: AgeStage,

Today, we're going to define exactly what AgeStage is. This lesson focuses on a single, powerful feature of Rust that is perfect for this task: the enum. Our learning outcome is to define four age stages (child, teen, adult, and elder) as an enum in Rust.

This will complete the basic data model for our character's identity in the Rust backend, making our code safer and more expressive.

1. Introducing Enums in Rust

In programming, we often encounter situations where a value can be one of a small, fixed set of possibilities. A character's life stage is a perfect example: they can be a Child, Teen, Adult, or Elder, but nothing else. While we could use a string or a number to represent this, those approaches are prone to errors (typos, invalid numbers).

Rust provides a much better solution: enumerations, or enums. An enum lets you define a custom type by enumerating all its possible variants.

Let's watch a short video to see the basic syntax for defining and using an enum.

Rust Programming Tutorial #10 - Enum Types

This video from the dcode channel provides a clear, concise introduction to the syntax and purpose of Rust enums.

Watch from the beginning to 01:19. Focus on how the enum keyword is used to declare the type and its variants, and how a variable is assigned a specific variant using the :: syntax.

As you saw, the syntax is straightforward. We use the enum keyword, give our type a name, and list the variants inside curly braces.


2. Implementing the AgeStage Enum

Now, let's apply this to our game. We will define the AgeStage enum in the same src/character.rs file where we created our Character and Personality structs.

Add the following code to src/character.rs:




#[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq, Clone, Copy)]
pub enum AgeStage {
    Child,
    Teen,
    Adult,
    Elder,
}

Let's break down the #[derive(...)] line:

  • Debug, Serialize, Deserialize: We include these just as we did for our structs. They allow us to easily print the enum for debugging and convert it to and from our JSON data format.
  • PartialEq, Eq: These traits allow us to compare two AgeStage values for equality (e.g., if character.age_stage == AgeStage::Adult).
  • Clone, Copy: These are performance optimizations. They allow Rust to make a cheap, bit-by-bit copy of AgeStage values instead of a more complex "move". Since our AgeStage variants don't contain any extra data, they are simple enough to be copied this way.

With this code, you have now fulfilled today's learning outcome! Our Character struct from the previous lesson is now complete and will compile without errors.


3. Making Decisions with match

Defining an enum is only half the story. The real power comes from using it to control the flow of your program. For example, a child might have different available interactions than an adult.

The primary tool for working with enums in Rust is the match expression. A match expression is like a switch statement on steroids. It takes a value (like an AgeStage) and lets you define a block of codeβ€”an "arm"β€”for each possible variant.

Crucially, match is exhaustive. This means the Rust compiler will force you to handle every single variant of the enum, preventing you from forgetting a case.

Let's see how it works in practice.

Rust Programming Tutorial #10 - Enum Types

The same video from dcode also provides a great introduction to the match expression.

Watch from 01:19 to 03:06. Notice how the match block has a separate line for each variant of the Direction enum.

Now, let's write a practical example for our game. Imagine we want a function that returns a descriptive string for a character's life stage. We can use match to do this cleanly and safely.

You can add this function to your src/character.rs file to test it out:

// This function takes an AgeStage and returns a static string slice.
pub fn get_age_stage_description(age_stage: AgeStage) -> &'static str {
    match age_stage {
        AgeStage::Child => "A young one, full of potential and mischief.",
        AgeStage::Teen => "Navigating the turbulent years of adolescence.",
        AgeStage::Adult => "In the prime of their life, building a future.",
        AgeStage::Elder => "A seasoned individual with a lifetime of stories.",
    }
}

// You could then use it like this in another function:
// let current_stage = AgeStage::Teen;
// let description = get_age_stage_description(current_stage);
// println!("{}", description); // Prints "Navigating the turbulent years of adolescence."

If you were to add a new AgeStage variant (e.g., YoungAdult) and forget to update this function, the Rust compiler would give you an error. This compile-time checking is a core feature that makes Rust so reliable.


4. A Glimpse Ahead: Enums with Data

Our AgeStage enum is simple: each variant is just a name. But Rust enums have another powerful feature: their variants can hold data. This allows you to model complex states where different variants require different information.

For example, later in the course, we might create an Event enum:

enum Event {
    Birthday(AgeStage), // The Birthday event needs to know which age stage is being celebrated.
    GotJob(String),     // This event needs to know the name of the job.
    FellAsleep,         // This event needs no extra data.
}

This single Event type can represent multiple, structurally different kinds of events in our game.

You don't need to implement this now, but understanding this capability is important. The following resource provides a great overview.

Enums - Rust By Example

The official 'Rust By Example' book has an excellent page on enums that demonstrates how variants can hold different kinds of data.

Read through the entire example. Pay close attention to the WebEvent enum. Notice how the KeyPress variant holds a char, Paste holds a String, and Click holds a struct-like object, all within the same enum.


Conclusion

Great work today! While this was a short lesson, you've mastered a fundamental and powerful concept in Rust that is central to building safe and robust game logic.

Key Takeaways:

  • enums are used to define a type that can only be one of a few predefined variants, making them perfect for representing fixed states like AgeStage.
  • Using an enum prevents invalid data at compile time, a significant advantage over using strings or integers.
  • The match expression is the primary way to work with enums. It is exhaustive, forcing you to handle every possible variant, which eliminates a common source of bugs.
  • Rust enums can also contain data, allowing you to model complex and varied states within a single type.

Now that our Character data model is fully defined in Rust, our next step is to let the player create their own characters. In the next lesson, we will shift back to Unity and build the character creator UI, including input fields for the character's name and sliders for their personality, and a dropdown to select the AgeStage we defined today.