Chapter 11

Tuples and Destructuring

What do you call an incredibly attractive tuple?
Irresistuple.

Sometimes a function needs to return two things. Coming up with a type name for that is not always worth it.

Rust has support for tuples, which are fixed-size groups of values. Unlike a Vec, the elements can be different types, and the size is part of the type.

let response: (u16, &str, bool) = (200, "ok", true);
let pair = (1, 2); // type inferred as (i32, i32)

You access fields by index with a dot:

if response.2 {
    println!("cached response: {}", response.0);
}

I prefer destructuring here so I can read status and cached rather than remember what .0 and .2 mean. It pulls the parts out into named bindings in one step.

let (status, message, cached) = response;
if cached {
    println!("cached response: {status} {message}");
}

// 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 (_, _, cached) = response;

Tuples are great for short-lived "two or three values that belong together" situations. When the tuple grows or you keep passing the same tuple type 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.

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

Useful from the Standard Library

  • 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.

    Syntax to Use

    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 Resources

      • Rust by Example: destructuring tuples shows tuple patterns in match arms.
      • Field-by-index access (full_name.0) also works, but a destructure with a meaningful name like first makes the function body easier to read.
      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

        Return the two values in reverse order, using tuple destructuring.

        This tuple contains only Copy values. After the tests pass, try using the original pair after passing it to swap_values. Why does that work, while using the original name tuple after get_first_name does not?

        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; does not move the second field.
          • Fields bound by value are moved or copied according to their types: String fields move, while integer fields copy.
          • 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

          Optional Chapters

          Extra practice to explore at your own pace. These chapters do not count toward course progress.

          ← Return to the Rust course curriculum