RustTuples

Tuples in Rust

A tuple is a fixed-size, ordered collection of values that can each have a different type. Tuples are useful when you want to group together a small number of values without defining a named struct.

Creating Tuples

You create a tuple by writing a comma-separated list of values inside parentheses. Type annotations are optional when the compiler can infer them.

RUST
fn main() {
    // Explicit type annotation
    let tup: (i32, f64, u8) = (500, 6.4, 1);

    // Inferred types
    let point = (3.0, -1.5);
    let mixed = (42, true, 'z', "hello");

    println!("{:?}", tup);
    println!("{:?}", point);
    println!("{:?}", mixed);
}
Note
Tuples have a fixed length — once declared, you cannot add or remove elements. The length is part of the type: `(i32, f64)` and `(i32, f64, u8)` are two distinct types.
Accessing Elements by Index

You can access individual tuple elements using dot notation followed by a zero-based index.

RUST
fn main() {
    let tup = (500, 6.4, true);

    let five_hundred  = tup.0;
    let six_point_four = tup.1;
    let flag          = tup.2;

    println!("{}", five_hundred);
    println!("{}", six_point_four);
    println!("{}", flag);
}
Destructuring Tuples

Destructuring lets you bind each element of a tuple to its own variable in a single let statement. This is often cleaner than accessing elements by index.

RUST
fn main() {
    let tup = (500, 6.4, 1);

    let (x, y, z) = tup;

    println!("x = {}", x);
    println!("y = {}", y);
    println!("z = {}", z);
}
Tip
Use `_` to ignore elements you do not need: `let (x, _, z) = tup;` discards the middle value without a compiler warning about unused variables.
Returning Multiple Values from Functions

Rust functions can only return a single value, but that value can be a tuple. This is the idiomatic way to return multiple results without defining a new type.

RUST
fn min_max(data: &[i32]) -> (i32, i32) {
    let mut min = data[0];
    let mut max = data[0];
    for &val in data.iter() {
        if val < min { min = val; }
        if val > max { max = val; }
    }
    (min, max)
}

fn main() {
    let numbers = [3, 1, 4, 1, 5, 9, 2, 6];
    let (lo, hi) = min_max(&numbers);
    println!("min = {}, max = {}", lo, hi);
}
The Unit Tuple `()`

The empty tuple () is called the unit type. It represents the absence of a meaningful value — similar to void in other languages. Functions that do not explicitly return anything actually return ().

RUST
fn greet(name: &str) -> () {
    println!("Hello, {}!", name);
}

fn main() {
    let result = greet("Rustacean"); // result has type ()
    println!("{:?}", result);        // prints ()
}
Note
`()` is both a type and its only value. You will see it as the `Ok` variant in `Result<(), Error>` when a function succeeds but has nothing useful to return.
Nested Tuples

Tuples can be nested inside other tuples. Each nesting level is its own type.

RUST
fn main() {
    let nested = ((1, 2), (3, 4));

    // Access with chained dot notation
    println!("nested.0.0 = {}", nested.0.0);
    println!("nested.0.1 = {}", nested.0.1);
    println!("nested.1.0 = {}", nested.1.0);

    // Destructure nested tuples
    let ((a, b), (c, d)) = nested;
    println!("a={} b={} c={} d={}", a, b, c, d);
}
Pattern Matching on Tuples

Tuples work naturally in match expressions. You can match on specific values, bind variables, or use _ as a wildcard.

RUST
fn classify_point(point: (i32, i32)) -> &'static str {
    match point {
        (0, 0) => "origin",
        (x, 0) if x > 0 => "positive x-axis",
        (0, y) if y > 0 => "positive y-axis",
        (x, y) if x > 0 && y > 0 => "first quadrant",
        _ => "somewhere else",
    }
}

fn main() {
    println!("{}", classify_point((0, 0)));
    println!("{}", classify_point((5, 0)));
    println!("{}", classify_point((3, 7)));
    println!("{}", classify_point((-2, 4)));
}
Printing Tuples

Tuples do not implement the Display trait, so you cannot use {} to print them. Use the Debug format {:?} (or the pretty-print {:#?}) instead.

RUST
fn main() {
    let t = (1, "hello", 3.14);

    // println!("{}", t);  // compile error: doesn't implement Display
    println!("{:?}", t);   // debug format — works
    println!("{:#?}", t);  // pretty-print debug
}
The Swap Pattern

Tuples make swapping two values clean and allocation-free — no temporary variable required.

RUST
fn main() {
    let mut a = 10;
    let mut b = 20;

    println!("before: a={}, b={}", a, b);

    (a, b) = (b, a); // tuple swap

    println!("after:  a={}, b={}", a, b);
}
Tuples and the Copy Trait

A tuple implements Copy if and only if every element type implements Copy. If any element is a String or another heap-allocated type, the tuple as a whole does not implement Copy and will be moved on assignment.

RUST
fn main() {
    // (i32, f64) is Copy — both fields are Copy types
    let t1 = (1, 2.0);
    let t2 = t1; // t1 is copied, not moved
    println!("{:?} {:?}", t1, t2); // both are valid

    // (i32, String) is NOT Copy — String is not Copy
    let t3 = (42, String::from("hello"));
    let t4 = t3; // t3 is moved into t4
    // println!("{:?}", t3); // compile error: t3 was moved
    println!("{:?}", t4);
}
Tuples vs Structs

Both tuples and structs group multiple values together. The choice between them depends on whether the fields need names.

Feature

Tuple

Struct

Field access

By index: t.0, t.1

By name: s.x, s.y

Readability

Low for many fields

High — names document intent

Syntax overhead

Minimal — no declaration needed

Requires a struct definition

Best for

Small ad-hoc groupings

Domain entities with meaning

Pattern matching

Yes

Yes

Display

Debug only

Debug only (unless you impl Display)

Tip
If you find yourself writing `t.0` and `t.1` in many places and the meaning is not obvious from context, consider upgrading to a named struct. The struct conveys intent; the tuple does not.
Practical Size Limit

Rust automatically implements standard traits (like Debug, Clone, PartialEq) for tuples up to 12 elements. You can create tuples with more than 12 fields, but they lose most trait implementations and become awkward to use. If you need more than 4 or 5 fields, a struct is almost always the better choice.

Common Use Cases
  • Returning multiple values from a function without defining a struct.

  • Grouping a status code with a response body: (StatusCode, ResponseBody).

  • Swapping two variables without a temporary.

  • Pattern matching on a pair of conditions simultaneously.

  • Storing a small, unnamed, short-lived grouping of values.

Summary
  • Tuples hold a fixed number of values of potentially different types.

  • Access elements with .0, .1, etc., or destructure with let (a, b, c) = tup;.

  • The unit type () is the empty tuple and is the implicit return type of functions with no explicit return.

  • Tuples implement Copy only when all their elements do.

  • Use {:?} (Debug) to print tuples — they do not implement Display.

  • Prefer named structs over tuples when the meaning of each field is not obvious from context.

  • Rust implements standard traits for tuples up to 12 elements.