RustArena

Step 3 of 7

Variants that carry data

Variants can hold data, and each variant can hold different data. This is where enums go far beyond what most languages call an "enum".

enum Shape {
    Circle(f64),
    Rectangle { width: f64, height: f64 },
    Square(f64),
}

fn area(shape: &Shape) -> f64 {
    match shape {
        Shape::Circle(radius) => std::f64::consts::PI * radius * radius,
        Shape::Rectangle { width, height } => width * height,
        Shape::Square(side) => side * side,
    }
}

fn main() {
    let shapes = [
        Shape::Circle(1.0),
        Shape::Rectangle { width: 3.0, height: 4.0 },
        Shape::Square(2.5),
    ];
    for s in &shapes {
        println!("{:.2}", area(s));
    }
}
3.14
12.00
6.25

Because area takes &Shape, the bindings are references too (radius is a &f64). Arithmetic works through them as written, but a comparison needs a * to read the number: *radius > 10.0. Without it, the compiler says expected `&f64`, found floating-point number and suggests the *.

Make invalid states unrepresentable

Here's how shapes often look in languages without data-carrying enums:

struct Shape {
    kind: String,   // "circle", "rectangle" or "square"
    radius: f64,    // only meaningful for circles
    width: f64,     // only meaningful for rectangles
    height: f64,    // only meaningful for rectangles
    side: f64,      // only meaningful for squares
}

Every shape carries four fields it doesn't need. Nothing stops kind: "cirlce", or a circle with a width but a radius of 0, and every function has to remember which fields to trust. With the enum, a circle has a radius and nothing else; a wrong combination can't be built. When you design types at work, look for "a kind field plus fields that only apply to some kinds": that's an enum waiting to happen.

Common mistakes

Your task

Write fn area(shape: &Shape) -> f64 for circles, rectangles and triangles (a triangle's area is base × height / 2). Use std::f64::consts::PI for π.

Challenge 2

Write fn perimeter(shape: &Shape) -> f64: a circle's perimeter is 2 × π × r, a rectangle's is 2 × (width + height), a square's is 4 × side.

Challenge 3

Model the ways a customer can pay. Define an enum Payment with three variants:

Then write fn describe(p: &Payment) -> String that returns "cash", "card ending in 4242" or "voucher worth 500 cents".

Previous: match: exhaustive by design Next: Option: a value that might be missing