Chapter 11

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]);

You only need a rough reading of the min_max body for now. These details are enough to follow the example:

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 keep passing the same shape around, give those fields names with a struct instead.

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
    
                
    Reveal the full solution Spoiler: the complete answer
    /// Returns a user's name and age as a tuple.
    /// For example, return "Alice" and 25.
    /// Useful for functions that need to return multiple values.
    fn get_user_info() -> (String, u32) {
        ("Alice".to_string(), 25)
    }
    
    #[test]
    fn test_get_user_info() {
        let (name, age) = get_user_info();
        assert_eq!(name, "Alice");
        assert_eq!(age, 25);
    }
    

    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. The dimensions in the tests keep both u32 results within range.
    • 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
      
                  
      Reveal the full solution Spoiler: the complete answer
      /// Calculates both area and perimeter of a rectangle.
      /// Returns (area, perimeter) as a tuple.
      fn rectangle_measurements(width: u32, height: u32) -> (u32, u32) {
          let area = width * height;
          let perimeter = 2 * (width + height);
          (area, perimeter)
      }
      
      #[test]
      fn test_rectangle_measurements() {
          let (area, perimeter) = rectangle_measurements(5, 3);
          assert_eq!(area, 15); // 5 * 3
          assert_eq!(perimeter, 16); // 2 * (5 + 3)
      }
      

      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.

      Ownership still applies when you destructure. Passing a tuple of Strings by value moves the whole tuple into the function, so the caller cannot use it afterward. A tuple of integers is Copy, which gives the function its own copy and leaves the caller's value usable. This is the same move-versus-copy distinction you worked through in the moves chapter.

      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, such as String, moves when destructured by value. A tuple is only Copy when all of its elements are Copy.
      Exercise 3 of 4
      Open in Web Editor

      Results

        Compiler / runtime output
        
                    
        Reveal the full solution Spoiler: the complete answer
        /// Extracts the first and last names from a full name tuple.
        /// Takes a tuple (`first_name`, `last_name`) and returns just the first name.
        ///
        /// Note: this signature moves the tuple in (it contains `String`s, which
        /// aren't `Copy`). After the call, the original `full_name` is no longer
        /// valid; try using it after calling this function and read the error.
        /// `swap_values` below takes `(i32, i32)`, which is `Copy`, so the
        /// original is still usable. Chapter 12 covers this properly.
        ///
        /// Hint: Use [tuple destructuring](https://doc.rust-lang.org/rust-by-example/flow_control/match/destructuring/destructure_tuple.html)
        fn get_first_name(full_name: (String, String)) -> String {
            let (first_name, _last_name) = full_name;
            first_name
        }
        
        #[test]
        fn test_get_first_name() {
            let full_name = ("John".to_string(), "Doe".to_string());
            assert_eq!(get_first_name(full_name), "John");
        }
        

        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
          
                      
          Reveal the full solution Spoiler: the complete answer
          /// Swaps two values using tuple destructuring.
          fn swap_values(pair: (i32, i32)) -> (i32, i32) {
              let (first, second) = pair;
              (second, first)
          }
          
          #[test]
          fn test_swap_values() {
              assert_eq!(swap_values((1, 2)), (2, 1));
              assert_eq!(swap_values((42, 100)), (100, 42));
          }
          

          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, a struct can give the fields names.
          • 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 12Option<T>: When a value might be missing