Question
Given the following Rust enum and value:
struct Point {
x: f64,
y: f64,
}
enum Shape {
Circle(Point, f64),
Rectangle(Point, Point),
}
let my_shape = Shape::Circle(Point { x: 0.0, y: 0.0 }, 10.0);
How can you access and print the second value stored in the Circle variant, which is 10.0? Attempts such as my_shape.last and my_shape.second do not work.
Short Answer
Rust enum variants can hold different kinds of data, so their contents are accessed by pattern matching, not by positional properties such as .second. You will learn to extract a circle's radius with match or if let, choose the right tool, and avoid ownership-related surprises.
Concept
An enum represents a value that can be one of several variants. In this example, a Shape is either:
Shape::Circle(Point, f64): a center point and a radiusShape::Rectangle(Point, Point): two points
Because each variant can contain a different number and type of values, Rust cannot provide a universal field like my_shape.second. A Rectangle has two Point values, while a Circle has a Point and an f64.
Instead, use pattern matching to ask Rust which variant you have and bind its stored values to local variables. This is type-safe: Rust only lets you use radius inside code where it has proved that the shape is a Circle.
For tuple-style enum variants such as Circle(Point, f64), values are extracted by writing a matching pattern:
Shape::Circle(center, radius)
Here, center receives the Point and radius receives the f64.
Mental Model
Think of Shape as a labeled package that may contain different contents.
- A package labeled
Circlecontains a center coordinate and a radius. - A package labeled
Rectanglecontains two corner coordinates.
Before taking out the second item, you must inspect the package label. Pattern matching is Rust's safe way to open the correct kind of package and name the contents.
my_shape.second would mean every possible Shape has one consistent second field. That is not true, so Rust requires you to handle the variant first.
Syntax and Examples
Use match when you want to handle every possible variant:
struct Point {
x: f64,
y: f64,
}
enum Shape {
Circle(Point, f64),
Rectangle(Point, Point),
}
fn main() {
let my_shape = Shape::Circle(Point { x: 0.0, y: 0.0 }, 10.0);
match my_shape {
Shape::Circle(_, radius) => println!("Circle radius: {radius}"),
Shape::Rectangle(_, _) => println!("This is a rectangle."),
}
}
The _ pattern means “there is a value here, but do not bind it because it is not needed.” The variable radius receives 10.0.
If you only care about the Circle case, use if let:
if let Shape::Circle(_, radius) = my_shape {
println!("Circle radius: {radius}");
}
if let runs its body only when my_shape is a Circle. If it is a Rectangle, nothing is printed.
Step by Step Execution
Consider this code:
let my_shape = Shape::Circle(Point { x: 0.0, y: 0.0 }, 10.0);
if let Shape::Circle(_, radius) = my_shape {
println!("{radius}");
}
Step by step:
my_shapestores the enum valueShape::Circle(...).- The
if letpattern checks whether the value has theShape::Circlevariant. - It does match, because the value is a circle.
_matches the first stored value, thePoint, and intentionally ignores it.radiusis bound to the second stored value,10.0.println!prints10or10.0depending on Rust's default display formatting for the value.
If my_shape contained Shape::Rectangle(...), the pattern would not match and the body would be skipped.
Real World Use Cases
Pattern matching enum data is common whenever a program has several valid kinds of state or result:
- UI drawing: draw a circle using its center and radius, or draw a rectangle using its corners.
- API results: extract successful data from
Result::Ok(value)or handleResult::Err(error). - Optional values: access an existing value in
Option::Some(value)while handlingNone. - Events: respond differently to events such as
Click,KeyPress, andWindowResized. - Parsers: process tokens such as numbers, identifiers, operators, and strings.
- Application state: handle states like
Loading,Ready(data), andFailed(message).
Enums make these alternatives explicit, and pattern matching makes handling each alternative safe.
Real Codebase Usage
In production Rust code, developers usually give variant fields meaningful names when the data has a clear role. Named fields can be easier to read than tuple positions:
struct Point {
x: f64,
y: f64,
}
enum Shape {
Circle { center: Point, radius: f64 },
Rectangle { top_left: Point, bottom_right: Point },
}
fn print_radius(shape: &Shape) {
if let Shape::Circle { radius, .. } = shape {
println!("Radius: {radius}");
}
}
.. means “ignore the other fields.” Passing &Shape and matching against shape borrows the shape rather than moving it, so the caller can continue using it afterward.
Use these common patterns:
- Use
matchwhen every variant needs an outcome. - Use
if letfor one variant of interest. - Use
let ... elsewhen a specific variant is required before the function can continue. - Use named variant fields when positional fields become unclear.
- Match on
&valuewhen you need to inspect a value without taking ownership of it.
Common Mistakes
Trying to use a field that does not exist
This does not compile:
// let radius = my_shape.second;
Shape is an enum, not a struct with a field named second. It may contain different variants with different data.
Forgetting to handle all variants with match
This is incomplete:
match my_shape {
Shape::Circle(_, radius) => println!("{radius}"),
}
Rust rejects it because my_shape could be a Rectangle. Add a Rectangle arm or use if let if ignoring rectangles is intentional.
match my_shape {
Shape::Circle(_, radius) => println!("{radius}"),
Shape::Rectangle(_, _) => {}
}
Accidentally moving the enum value
Matching by value can move data out of an enum:
let shape = Shape::Circle(Point { x: 0.0, y: 0.0 }, 10.0);
match shape {
Shape::Circle(_, radius) => println!("{radius}"),
Shape::Rectangle(_, _) => {}
}
// `shape` may not be usable here because it was moved.
To inspect without consuming it, match on a reference:
if let Shape::Circle(_, radius) = &shape {
println!("{radius}");
}
Using tuple fields when names would improve readability
Circle(Point, f64) is valid, but readers must remember that the second item is the radius. For larger domain models, prefer Circle { center, radius }.
Comparisons
| Approach | Best when | Example |
|---|---|---|
match | Every enum variant needs handling | match shape { Shape::Circle(..) => ..., Shape::Rectangle(..) => ... } |
if let | You care about one variant and can ignore the others | if let Shape::Circle(_, radius) = shape { ... } |
let ... else | A function can continue only for one expected variant | let Shape::Circle(_, radius) = shape else { return; }; |
| Tuple variant | Values are short and their order is obvious | Circle(Point, f64) |
| Struct-like variant | Values have meaningful roles and readability matters | Circle { center: Point, radius: f64 } |
A regular struct has fields that can be accessed directly:
struct Circle {
center: Point,
radius: f64,
}
let circle = Circle {
center: Point { x: 0.0, y: 0.0 },
radius: 10.0,
};
println!("{}", circle.radius);
An enum needs matching first because its current variant determines which data exists.
Cheat Sheet
// Extract tuple-variant values with match
match shape {
Shape::Circle(center, radius) => println!("{radius}"),
Shape::Rectangle(a, b) => println!("rectangle"),
}
// Ignore values with `_`
if let Shape::Circle(_, radius) = shape {
println!("{radius}");
}
// Borrow instead of moving the enum
if let Shape::Circle(_, radius) = &shape {
println!("{radius}");
}
// Match a named-field variant
if let Shape::Circle { radius, .. } = &shape {
println!("{radius}");
}
// Require one variant, otherwise leave early
let Shape::Circle(_, radius) = shape else {
return;
};
Rules:
- Enum contents are accessed with patterns, not
.first,.second, or.last. matchmust cover all possible variants._ignores one value;..ignores remaining fields.- Matching by value can move the value; use
&shapeto borrow it.
FAQ
Can I access a Rust enum tuple field with .0 or .1?
No. .0 and .1 work on tuples and tuple structs, but not directly on an enum because the enum may currently be a different variant. Match the variant first.
How do I print the radius only if the shape is a circle?
Use if let:
if let Shape::Circle(_, radius) = &shape {
println!("{radius}");
}
Why does match require a rectangle branch too?
A Shape can be a Circle or a Rectangle. Rust requires exhaustive matching so no possible case is silently forgotten.
Does pattern matching copy the radius?
It depends on how you match. Matching shape by value moves its contents. Matching &shape borrows them. Since f64 implements Copy, the radius is easy to copy when needed, but the surrounding enum may still be moved by a by-value match.
Should I use tuple variants or named fields?
Use tuple variants for small, obvious groups of values. Use named fields when field roles matter, such as Circle { center, radius }.
Can I extract the radius in a function?
Yes. For example:
fn radius_of(shape: &Shape) -> Option<f64> {
match shape {
Shape::Circle(_, radius) => Some(*radius),
Shape::Rectangle(_, _) => None,
}
}
Mini Project
Description
Build a small shape-reporting program. It stores circles and rectangles in a vector, then uses pattern matching to print useful information for each shape. This demonstrates why enum data must be extracted according to its active variant.
Goal
Print a description for every shape and calculate the area of each circle.
Requirements
Create Point and Shape types.
Store at least two circles and one rectangle in a vector.
Use match to handle both Shape variants.
Print each circle's center, radius, and area.
Print each rectangle's two points.
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Accessing Cargo Package Metadata in Rust
Learn how to read Cargo package metadata like version, name, and authors in Rust using compile-time environment macros.