Chapter 9

Tuples and Destructuring

👋 Anyone can read and edit this exercise. Sign up to save your progress.

A tuple is a fixed-size group of values. Unlike a Vec, the elements can be different types, and the size is part of the type.

let user: (String, u32) = ("Alice".to_string(), 25);
let pair = (1, 2);                 // type inferred as (i32, i32)
let triple = ("ok", 200, true);    // (&str, i32, bool)

You access fields by index with a dot:

let name = user.0;
let age = user.1;

But the more idiomatic way is destructuring: pull the parts out into named bindings in one step.

let (name, age) = user;
let (a, b) = (1, 2);

// Functions can return tuples for multiple values:
fn min_max(values: &[i32]) -> (i32, i32) {
    (*values.iter().min().unwrap(), *values.iter().max().unwrap())
}
let (lo, hi) = min_max(&[3, 1, 4, 1, 5, 9]);

That min_max body has three pieces of syntax we haven't formally introduced yet. Don't let them trip you up here:

When you only care about some fields, use _ to ignore the rest:

let (first, _) = ("Alice", "Smith");

Tuples are great for short-lived "two or three values that belong together" situations. When the tuple grows or you find yourself passing it around a lot, that's a hint to define a struct instead (chapter 12).

Returning multiple values

Functions in Rust return a single value, but a tuple lets you bundle several values into that single return. It's the lightest-weight way to hand back more than one thing without defining a new type.

Here you'll return a (String, u32) pair: a name and an age.

Useful from the standard library

  • The Rust Book on tuples covers tuple syntax and how the type signature is just the parenthesized list of element types.
  • String::from or .to_string() on a &str literal gets you the owned String the tuple wants in its first slot.
Exercise 1 of 4
Open in Web Editor

Results

    Compiler / runtime output
    
                

    Computing two values at once

    When two results are naturally produced together, returning them as a tuple is often clearer than two separate function calls. The caller destructures the result into named bindings.

    Useful from the standard library

    • The arithmetic operators * and + are all you need here. Both u32 results fit easily for any sane rectangle.
    • Tuple construction is just parentheses: (area, perimeter). The return type (u32, u32) already tells the compiler what shape to expect.
    • The caller in the test uses let (area, perimeter) = ... to destructure the return into named bindings, the mirror image of how you build it.
    Exercise 2 of 4
    Open in Web Editor

    Results

      Compiler / runtime output
      
                  

      Destructuring a tuple parameter

      You can destructure a tuple right in the function parameter list, or inside the body with a let binding. Either way, you pull out the pieces by position.

      Watch out for ownership: a tuple of Strings is moved into the function, while a tuple of integers is copied. "Moved" means the caller's binding is no longer usable afterwards, because the value's single owner is now the function parameter rather than the caller. "Copied" means the value is duplicated bit-for-bit, so the caller keeps theirs and the function gets its own. The split is decided by a trait called Copy: types that are tiny and have no heap data (integers, bools, char, fixed-size arrays of those, and tuples made entirely of Copy types) implement it; types that own heap data (like String or Vec) deliberately don't. The doc-comment below has more on this, and chapter 11 covers move semantics in depth.

      Useful from the standard library

      • Rust by Example: destructuring tuples shows the let (a, b) = pair; form and how _ can ignore parts you don't want to bind.
      • Field-by-index access (full_name.0) also works, but a destructure with a meaningful name like first reads better at the call site.
      • Anything that isn't Copy (like String) is moved when bound by destructuring, so the caller's binding becomes unusable: ownership of the underlying heap buffer transferred into the function. Copy types (integers, bools, char, and tuples of those) are duplicated instead, so the caller keeps their copy. After this function returns, the caller's (String, String) tuple is gone. Chapter 11 covers move semantics in depth.
      Exercise 3 of 4
      Open in Web Editor

      Results

        Compiler / runtime output
        
                    

        Swapping with destructuring

        Tuple destructuring makes swapping two values a one-liner: bind the pair to (a, b) and return (b, a). No temporary variable, no manual juggling.

        Useful from the standard library

        • std::mem::swap swaps two &mut T references in place. Useful when you can't take ownership; here, returning a fresh tuple is cleaner.
        • The integers in this exercise are Copy, so (b, a) makes bit-wise copies of both. No moves to worry about.
        Exercise 4 of 4
        Open in Web Editor

        Results

          Compiler / runtime output
          
                      

          Wrapping up tuples and destructuring

          You used tuples to return multiple values, destructured them in parameter lists and let bindings, and saw how ownership behaves differently for Copy and non-Copy element types.

          What we learned

          • A tuple is a fixed-size group of values whose size and per-slot types are part of the type. (String, u32) and (u32, String) are different types.
          • Build a tuple with parentheses; access fields with .0, .1, etc. Destructuring with let (a, b) = pair; is usually clearer.
          • Tuples are the lightest-weight way to return more than one value from a function. When the same tuple shows up in many places or grows past two or three fields, switch to a struct (chapter 12).
          • Use _ in a pattern to ignore a field: let (first, _) = pair;.
          • Move vs. copy still applies: a tuple of Strings moves on destructure, a tuple of integers copies. The element types decide.
          • The unit type () is the empty tuple. It's what functions "without a return value" actually return.
          Next chapter 10Option<T>: When a Value Might Be Missing