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
Circle(f64)is a tuple-like variant: its data is unnamed, like a tuple struct. Create one withShape::Circle(1.0).Rectangle { width: f64, height: f64 }is a struct-like variant, with named fields. Create one withShape::Rectangle { width: 3.0, height: 4.0 }.- In a pattern,
Shape::Circle(radius)matches circles and puts the data into a new variableradius.Shape::Rectangle { width, height }binds both fields to variables of the same name. - All three are the same type,
Shape, so they fit in one array and one function handles them all.
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
- A pattern that forgets a field:
Shape::Rectangle { width }giveserror[E0027]: pattern does not mention field `height`. Name every field, or writeShape::Rectangle { width, .. }to ignore the rest (..gets its full story in the patterns module). - Using a data variant without data:
let s: Shape = Shape::Circle;is an error (expected `Shape`, found enum constructor).Shape::Circlealone is a function that builds a circle; call it:Shape::Circle(1.0).
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:
Cash(no data),Card { last4: String }(the last four digits of the card),Voucher(u32)(the voucher's value in cents).
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