Vibe Engines
YouTube

Lesson 5.3 · 8 min

One of several: enum

Chatterbox decides what to do by comparing strings like "/help". A typo in one of them compiles fine and fails at run time. An enum lists every possible command once, and the compiler holds you to it.

Adds to ChatterboxA Command enum, matched exhaustively.

A value that is exactly one of these

A struct says "this and this and this". An enum says "this or that or the other". A line you type to Chatterbox is one of a few things: ask for help, quit, say something, or something it does not understand.

Each option is a variant, and a variant can carry its own data. Say carries the text to say; Unknown carries the line it could not read; Help and Quit carry nothing.

One enum, four variants, one match
enum Command {
Help,
Quit,
Say(String),
Unknown(String),
}
 
fn handle(cmd: Command) -> String {
match cmd {
Command::Help => String::from("commands: /help /quit /say <text>"),
Command::Quit => String::from("bye"),
Command::Say(text) => text,
Command::Unknown(line) => format!("unknown: {line}"),
}
}
 
fn main() {
println!("{}", handle(Command::Say(String::from("hello"))));
println!("{}", handle(Command::Quit));
}
$ cargo run
hello
bye

In match (lesson 3.2) you matched text. Here each arm names a variant, and Say(text) also pulls the data out and gives it a name inside that arm. Because cmd is owned, text is moved out of it: the arm simply hands the string on.

Now delete an arm and see what the compiler thinks.

A variant nobody handled
enum Command {
Help,
Quit,
Say(String),
Unknown(String),
}
 
fn handle(cmd: Command) -> String {
match cmd {
Command::Help => String::from("help"),
Command::Quit => String::from("bye"),
Command::Say(text) => text,
}
}
 
fn main() {
println!("{}", handle(Command::Quit));
}
error[E0004]: non-exhaustive patterns: `Command::Unknown(_)` not covered
  --> src/main.rs:9:11
   |
 9 |     match cmd {
   |           ^^^ pattern `Command::Unknown(_)` not covered
   |
note: `Command` defined here
  --> src/main.rs:1:6
   |
 1 | enum Command {
   |      ^^^^^^^
...
 5 |     Unknown(String),
   |     ------- not covered
   = note: the matched value is of type `Command`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
   |
12 ~         Command::Say(text) => text,
13 ~         Command::Unknown(_) => todo!(),
   |

E0004: "non-exhaustive patterns: Command::Unknown(_) not covered". The compiler lists the case you forgot. A match must cover every variant, so adding a new command later turns every match that forgot it into a compile error. That is the whole value: a string comparison can only fail at run time, an enum fails at build time.

From a line to a Command

Something has to turn the typed line into a variant. That is parse. It borrows the line as a &str and returns a brand-new Command that owns whatever it carries.

PredictParse a line
enum Command {
Help,
Quit,
Say(String),
Unknown(String),
}
 
fn parse(line: &str) -> Command {
match line.trim() {
"/help" => Command::Help,
"/quit" => Command::Quit,
other => {
if other.starts_with("/say ") {
Command::Say(String::from(&other[5..]))
} else {
Command::Unknown(String::from(other))
}
}
}
}
 
fn main() {
match parse(" /say hi there ") {
Command::Say(t) => println!("say: [{t}]"),
Command::Unknown(l) => println!("unknown: [{l}]"),
_ => println!("other"),
}
}

What does this print?

  • say: [hi there]
  • unknown: [/say hi there]
  • other
Reveal the answer

The program prints: say: [hi there] trim() removes the outer spaces first, so the line becomes /say hi there. It does not match /help or /quit, falls to other, starts with /say , and &other[5..] is everything after those five characters.

src/main.rs: Chatterbox v0.5, commands are values
enum Command {
Help,
Quit,
Say(String),
Unknown(String),
}
 
fn parse(line: &str) -> Command {
match line.trim() {
"/help" => Command::Help,
"/quit" => Command::Quit,
other => {
if other.starts_with("/say ") {
Command::Say(String::from(&other[5..]))
} else {
Command::Unknown(String::from(other))
}
}
}
}
 
fn handle(cmd: Command) -> String {
match cmd {
Command::Help => String::from("commands: /help /quit /say <text>"),
Command::Quit => String::from("bye"),
Command::Say(text) => text,
Command::Unknown(line) => format!("unknown: {line}"),
}
}
 
fn main() {
for line in ["/help", "/say hello", "/dance", "/quit"] {
println!("{}", handle(parse(line)));
}
}
$ cargo run
commands: /help /quit /say <text>
hello
unknown: /dance
bye

Nearly the whole file is new, because the old string comparisons moved into parse and handle. Look at main: it is now one line of real logic. Parsing is in one place, handling is in another, and the enum is the contract between them.

Try itYour turn: label a command

Return a short label for a command, borrowed: help, quit, say:<text> or ?<line> for unknown. Cover every variant.

Return a short label for a command, borrowed: help, quit, say:<text> or ?<line> for unknown. Cover every variant.

enum Command {
Help,
Quit,
Say(String),
Unknown(String),
}
 
fn label(cmd: &Command) -> String {
// help, quit, say:<text>, ?<line>
String::new()
}
 
fn main() {
println!("{}", label(&Command::Say(String::from("hi"))));
}

Hidden tests (3): plain_variants say_carries_text unknown_carries_line

This exercise runs real Rust in your browser through the official Rust Playground, so it needs JavaScript.

Self-check

Say it out loud, then reveal

Self-checkRetrieval beats re-reading
What can a variant like <code>Say(String)</code> do that a plain constant cannot?Reveal
Carry data with it. The text travels inside the value and you pull it out in the matching arm.
What does E0004 protect you from?Reveal
Forgetting a case. A match must cover every variant, so adding a new one turns each match that does not handle it into a compile error.
Why does <code>parse</code> take <code>&amp;str</code> but return an owned <code>Command</code>?Reveal
It only needs to read the line, so it borrows. The Command must outlive the call, so it owns its own copy of the text.

That is lesson 5.3. Tick it off so the course remembers where you are.

Learn Rust by building a chat app · Lesson 5.3 · every compiler message above is real output from rustc 1.99.0 (b940084d7 2026-09-28) (edition 2024) · Vibe Engines · 2026