Skip to content

Rust Tuples

A tuple groups several values of possibly different types into one. It’s a quick way to bundle related things together without defining a whole new type for them.

fn main() {
let person: (&str, i32, bool) = ("Alice", 30, true);
println!("{:?}", person);
}

Output:

("Alice", 30, true)
▶ Try it Yourself

Notice the tuple mixes a &str, an i32, and a bool in one value — something an array can’t do, since every element in an array must be the same type.

To print a whole tuple, use {:?} (debug formatting) instead of {}. Plain {} doesn’t work for tuples.

Use .0, .1, .2, and so on — tuples are indexed starting at zero:

fn main() {
let person = ("Alice", 30, true);
println!("Name: {}", person.0);
println!("Age: {}", person.1);
println!("Active: {}", person.2);
}

Output:

Name: Alice
Age: 30
Active: true
▶ Try it Yourself

The position must be a plain number written in the code. You can’t use a variable, like person.i, because each position can hold a different type, and Rust has to know the type at compile time.

You can pull every value out into its own variable in one line:

fn main() {
let person = ("Alice", 30, true);
let (name, age, is_active) = person;
println!("{name} is {age}, active: {is_active}");
}

Output:

Alice is 30, active: true
▶ Try it Yourself

This is often clearer than repeated .0, .1, .2 access, especially once a tuple has more than two or three fields.

Tuples are the natural way to return more than one value, since a function can only declare one return type:

fn min_max(numbers: &[i32]) -> (i32, i32) {
let min = *numbers.iter().min().unwrap();
let max = *numbers.iter().max().unwrap();
(min, max)
}
fn main() {
let (smallest, largest) = min_max(&[4, 8, 1, 9, 3]);
println!("min: {smallest}, max: {largest}");
}

Output:

min: 1, max: 9
▶ Try it Yourself

Don’t worry about &[i32], .iter() or .unwrap() yet. They’re covered on the Slices, Iterators and Option pages. The part to notice here is the return type (i32, i32) and how main destructures it into two variables right away.

If a tuple is declared with mut, you can change its fields one at a time, using the same .0, .1 positions. The values can change, but the shape can’t: a tuple always keeps the number and types of fields it started with.

Destructuring also works the other way: you can assign into existing variables. That gives you a neat one-line swap, with no temporary variable. If you only want some of the fields, write _ for the ones you don’t need:

fn main() {
let mut point = (3, 4);
point.0 += 10; // change one field in place
println!("point is {:?}", point);
let mut a = 1;
let mut b = 2;
(a, b) = (b, a); // swap the two values
println!("a = {a}, b = {b}");
let (x, _) = point; // keep x, ignore the second field
println!("x = {x}");
}

Output:

point is (13, 4)
a = 2, b = 1
x = 13
▶ Try it Yourself

The swap works because Rust builds the right side, (b, a), first. Only then does it unpack it into a and b. There’s much more you can do with _ and patterns on the Pattern Matching page.

You’ve actually already met a tuple without realizing it. () is called the unit type, and it’s what a function returns when it doesn’t return anything meaningful — it’s the implicit return type of fn main().

Tuples are great for quick, unnamed groupings — especially returning two or three values from a function. Once a group of values represents a real concept with named fields (like a Person with a name and age you’ll refer to by name throughout your code), a struct is usually the better, more readable choice.

Write a function sum_and_product(a: i32, b: i32) -> (i32, i32) that returns both the sum and the product of two numbers. In main, call it with 5 and 6 and destructure the result. Hint: the last line of the function is the tuple itself, (a + b, a * b), with no semicolon.

Show solution
fn sum_and_product(a: i32, b: i32) -> (i32, i32) {
(a + b, a * b)
}
fn main() {
let (sum, product) = sum_and_product(5, 6);
println!("sum = {sum}, product = {product}");
}

Output:

sum = 11, product = 30
▶ Try it Yourself

A rectangle can be stored as a tuple of two points, where each point is itself a tuple: let rect = ((1, 2), (5, 5));. Work out its width, height and area. Hint: tuples can be nested, and rect.1.0 means “the first field of the second point”.

Show solution
fn main() {
let rect = ((1, 2), (5, 5));
let width = rect.1.0 - rect.0.0;
let height = rect.1.1 - rect.0.1;
println!("width {width}, height {height}, area {}", width * height);
}

Output:

width 4, height 3, area 12
▶ Try it Yourself

You can also destructure both points at once: let ((x1, y1), (x2, y2)) = rect;. That’s longer to type, but x2 - x1 is easier to read than rect.1.0 - rect.0.0. When the dots start to pile up like this, it’s a sign a struct with named fields would be clearer.

  • A tuple groups values of possibly different types: (name, age, is_active).
  • Access fields with .0, .1, .2, or pull them all out at once with destructuring.
  • Tuples are a natural way to return multiple values from a function.
  • A mut tuple’s fields can change, but its size and types can’t. (a, b) = (b, a); swaps two values.
  • For named, reusable groupings, reach for a struct instead.

Quick check

1. What can a tuple hold that an array cannot?

2. How do you access the second element of a tuple called person?

3. After let mut a = 1; let mut b = 2; (a, b) = (b, a);, what are a and b?

Score: 0 / 3