Enums
Represent a closed set of named states and use match to handle each variant.
Core concepts
enum, variants, match, exhaustiveness
How the learner should approach this project
Do not paste the complete program immediately. Create the file, type the first step, run vpp check, and then run the program when a complete entry point exists. Read the explanation before looking at the snippet. After each step, predict what the new code should do. This turns the page into a lesson rather than a code dump.
Step 1: Define TrafficLight
An enum represents one value chosen from a fixed set of variants. The compiler knows the complete set, which makes exhaustive matching possible.
After typing the snippet, identify the new names introduced by the step. Ask what each name represents, what type the compiler should infer or check, and what value should exist after the code runs.
enum TrafficLight {
Red
Yellow
Green
}Step 2: Put the enum inside a struct
A struct field can use a user defined enum type. Signal now represents a value that carries a TrafficLight.
Before moving forward, explain how this step connects to the previous one. In particular, identify which values cross a function boundary, which values change, and which values are guaranteed by the type system.
struct Signal {
light: TrafficLight
}Step 3: Match every variant
match inspects signal.light and selects the arm corresponding to the active variant. Every TrafficLight variant is covered, so the match is exhaustive.
Before moving forward, explain how this step connects to the previous one. In particular, identify which values cross a function boundary, which values change, and which values are guaranteed by the type system.
fn action(signal: Signal) -> string {
match signal.light {
Red => {
return "stop"
}
Yellow => {
return "slow"
}
Green => {
return "go"
}
}
}Step 4: Construct and evaluate signals
The main function creates two Signal values and sends them through action. The returned strings show how enum state can drive program behavior.
Before moving forward, explain how this step connects to the previous one. In particular, identify which values cross a function boundary, which values change, and which values are guaranteed by the type system.
fn main() -> int {
let stop = Signal { light: Red }
let go = Signal { light: Green }
print(action(stop))
print(action(go))
return 0
}Complete source
enum TrafficLight {
Red
Yellow
Green
}
struct Signal {
light: TrafficLight
}
fn action(signal: Signal) -> string {
match signal.light {
Red => {
return "stop"
}
Yellow => {
return "slow"
}
Green => {
return "go"
}
}
}
fn main() -> int {
let stop = Signal { light: Red }
let go = Signal { light: Green }
print(action(stop))
print(action(go))
return 0
}Expected behavior
The complete program should produce the following output when run with the command shown below. Exact formatting should follow the current V++ runtime.
stop go
Run the project
vpp run projects/07-enums/main.vppWhat the learner should understand after this project
Enums model a closed set of states. match makes those states visible in code and lets the compiler reason about coverage.
Common mistakes to teach
- Leaving an enum variant out of match
- Using a variant that belongs to another enum
- Constructing a struct with an incompatible enum value
Practice extension
Add a fourth traffic state and observe which match sites the compiler requires you to update.
A strong learner should be able to explain the program without looking at the code, rebuild the core idea from memory, and make the practice change without copying a solution.
Full program
enum TrafficLight {
Red
Yellow
Green
}
struct Signal {
light: TrafficLight
}
fn action(signal: Signal) -> string {
match signal.light {
Red => {
return "stop"
}
Yellow => {
return "slow"
}
Green => {
return "go"
}
}
}
fn main() -> int {
let stop = Signal { light: Red }
let go = Signal { light: Green }
print(action(stop))
print(action(go))
return 0
}