Skip to content
Lesson 14 of 15

Step 1 of 7 · Reading · ~3 min

Learn

Structs, Enums, and Error Handling

Enums and Pattern Matching

An enum is a type whose value is exactly one of a fixed list of variants, and in Rust each variant can carry its own data. By the end you will define an enum with data-carrying variants and write a match that pulls that data back out, one arm per variant.

Defining an enum

A variant is one of the possible forms a value can take. A variant with no data is just a name; a tuple variant carries values in parentheses, like a tuple struct from the Structs lesson.

enum Parcel {
    Letter,             // no data
    Packet(i64),        // weight in kg
    Crate(i64, i64),    // weight in kg, floors to carry it up
}

let a = Parcel::Letter;
let b = Parcel::Packet(4);
let c = Parcel::Crate(7, 3);

a, b and c all have the same type, Parcel. You build a value with EnumName::Variant, and the variant decides what data travels with it: a Letter has no weight at all, so you cannot forget to set one or read one that is meaningless.

Taking the data back out with match

A match arm can be a pattern naming a variant plus variable names for its fields. When the arm matches, those names are bound to the values stored inside:

fn postage(p: &Parcel) -> i64 {
    match p {
        Parcel::Letter => 2,
        Parcel::Packet(kg) => 5 + kg * 3,
        Parcel::Crate(kg, floors) => kg * 10 + floors * 4,
    }
}

Trace it:

callarm takenboundresult
postage(&a)Letternothing2
postage(&b)Packet(kg)kg = 45 + 12 = 17
postage(&c)Crate(kg, floors)kg = 7, floors = 370 + 12 = 82

Fields are bound by position, not by name. Parcel::Crate(floors, kg) compiles too, and silently puts 7 in floors. Because p is a reference, kg is a &i64; arithmetic such as kg * 3 works on it directly and gives an i64.

Exhaustiveness

Delete the Letter arm and the program does not compile: error[E0004]: non-exhaustive patterns ... not covered. Every variant must have an arm, or you must add a catch-all _ =>. Prefer naming every variant: when someone adds a fourth kind of parcel later, the compiler points at every match that needs a new arm, while a _ arm would quietly swallow it.

if let: when only one variant matters

if let Parcel::Crate(kg, _) = c {
    println!("heavy crate: {} kg", kg);    // heavy crate: 7 kg
}

if let runs its block only when the pattern matches, and _ ignores a field you do not need. It checks no other variant, so use match whenever every case needs an answer.

The integer division trap

Integer / throws away the remainder, and * and / run left to right. The order therefore changes the answer:

let kg = 7;
let floors = 3;
println!("{}", kg / 4 * floors);   // 7/4 = 1, then 1*3 = 3
println!("{}", kg * floors / 4);   // 21/4 = 5

Multiply first, divide last, and you round down only once.

Common mistakes

  • Dividing too early. Halving a product as x / 2 * y truncates before multiplying: for 5 and 3 it gives 6 instead of 7.
  • Forgetting part of the formula in one arm. Every arm is its own formula. Leave out a / 2 and a triangle of 6 by 3 reports 18 instead of 9.
  • Using the wrong field. Writing w * w in a two-field arm compiles and gives 49 for a 7 by 3 rectangle instead of 21.
  • Computing the perimeter instead of the area. A square of side 3 has area 9, not 12.

Your exercise

The starter defines enum Shape and already reads the shapes and prints the results. You write the body of area: a match with one arm per variant, returning that shape's area. Watch the triangle: multiply before you divide.

Up nextError Handling: Result and OptionStructs, Enums, and Error Handling

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…