Multiple Return Values
Basics · Lesson 14
To return several values, a Rust function returns one tuple, and the caller unpacks it.
Coming from GoGo returns several values, but Rust returns one tuple, (i32, i32), and unpacks it with let (lo, hi) = .... Because it's one value, you can also keep it whole in a variable and read pair.0 and pair.1, which Go refuses. A Go (value, err) pair is written as a Result in Rust, not as a tuple.
Coming from KotlinA tuple is like Pair or Triple, with as many slots as you write, and it is destructured the same way. The fields are .0 and .1 rather than first and second.
Coming from PythonTuples work much like Python's: (lo, hi) to build, let (lo, hi) = ... to unpack, _ to skip. The type of each slot is fixed, and you read a slot with t.0, not t[0]. A Rust tuple in a mut variable can change in place; a Python tuple cannot.
Coming from TypeScript(i32, i32) is your [number, number] tuple type. A Rust tuple isn't an array, though: you read t.0 and t.1, you can't loop over it, and you skip a slot with _ instead of leaving it empty.
Coming from JavaJava has no built-in tuple type, so you'd write a small record; Rust's (i32, i32) is that record without a name or a declaration. You unpack it with let (lo, hi) = ..., and _ skips a value you don't need.
Coming from C#(i32, i32) is your value tuple (int, int), and let (lo, hi) = ... is deconstruction with _ as the discard. The difference is that Rust tuple fields have no names: you use .0 and .1, or reach for a struct.
Coming from CC has no tuples, so you return a small struct or write through pointers; Rust's (i32, i32) is like an anonymous struct you never have to declare, but its layout is not promised, so it is not a C struct. let (lo, hi) = ... unpacks it, and _ ignores a field.
Coming from C++(i32, i32) is std::pair<int, int> (or std::tuple), and let (lo, hi) = ... is a structured binding. _ skips a slot without naming it, and (a, b) = f(); assigns into existing variables, as std::tie does.
The quick comparison
fn min_max(xs: &[i32]) -> (i32, i32) { let mut lo = xs[0]; let mut hi = xs[0]; for &x in xs { // each item is &i32; the &x pattern takes the number out if x < lo { lo = x; } if x > hi { hi = x; } } (lo, hi)}
fn main() { let (lo, hi) = min_max(&[4, 9, 1, 7]); println!("{lo} {hi}");
let (_, only_hi) = min_max(&[3, 5]); // _ ignores a value println!("{only_hi}");}1 9 5
package main
import "fmt"
func minMax(xs []int) (int, int) { lo, hi := xs[0], xs[0] for _, x := range xs { if x < lo { lo = x } if x > hi { hi = x } } return lo, hi}
func main() { lo, hi := minMax([]int{4, 9, 1, 7}) fmt.Println(lo, hi)
_, onlyHi := minMax([]int{3, 5}) fmt.Println(onlyHi)}1 9 5
fun minMax(xs: List<Int>): Pair<Int, Int> { var lo = xs[0] var hi = xs[0] for (x in xs) { if (x < lo) lo = x if (x > hi) hi = x } return Pair(lo, hi)}
fun main() { val (lo, hi) = minMax(listOf(4, 9, 1, 7)) println("$lo $hi")
val (_, onlyHi) = minMax(listOf(3, 5)) println(onlyHi)}1 9 5
def min_max(xs: list[int]) -> tuple[int, int]: lo = hi = xs[0] for x in xs: if x < lo: lo = x if x > hi: hi = x return lo, hi
lo, hi = min_max([4, 9, 1, 7])print(f"{lo} {hi}")
_, only_hi = min_max([3, 5]) # _ ignores a valueprint(only_hi)1 9 5
// a tuple type: an array with a fixed length and a type per slotfunction minMax(xs: readonly number[]): [number, number] { let lo = xs[0]; let hi = xs[0]; for (const x of xs) { if (x < lo) lo = x; if (x > hi) hi = x; } return [lo, hi];}
const [lo, hi] = minMax([4, 9, 1, 7]);console.log(`${lo} ${hi}`);
const [, onlyHi] = minMax([3, 5]); // leave a slot empty to skip itconsole.log(onlyHi);1 9 5
import java.util.List;
public class Main { // Java has no built-in tuple, so a tiny record holds the two values record MinMax(int lo, int hi) {}
static MinMax minMax(List<Integer> xs) { int lo = xs.get(0); int hi = xs.get(0); for (int x : xs) { if (x < lo) lo = x; if (x > hi) hi = x; } return new MinMax(lo, hi); }
public static void main(String[] args) { var r = minMax(List.of(4, 9, 1, 7)); System.out.println(r.lo() + " " + r.hi());
int onlyHi = minMax(List.of(3, 5)).hi(); System.out.println(onlyHi); }}1 9 5
var (lo, hi) = MinMax([4, 9, 1, 7]);Console.WriteLine($"{lo} {hi}");
var (_, onlyHi) = MinMax([3, 5]); // _ ignores a valueConsole.WriteLine(onlyHi);
static (int Lo, int Hi) MinMax(int[] xs){ int lo = xs[0], hi = xs[0]; foreach (var x in xs) { if (x < lo) lo = x; if (x > hi) hi = x; } return (lo, hi);}1 9 5
#include <stdio.h>
// C has no tuples: return a small struct insteadstruct MinMax { int lo; int hi;};
struct MinMax min_max(const int *xs, size_t len) { struct MinMax r = {xs[0], xs[0]}; for (size_t i = 0; i < len; i++) { if (xs[i] < r.lo) r.lo = xs[i]; if (xs[i] > r.hi) r.hi = xs[i]; } return r;}
int main(void) { int a[] = {4, 9, 1, 7}; struct MinMax r = min_max(a, 4); printf("%d %d\n", r.lo, r.hi);
int b[] = {3, 5}; printf("%d\n", min_max(b, 2).hi); // only use the field you need return 0;}1 9 5
#include <iostream>#include <tuple>#include <utility>#include <vector>
std::pair<int, int> min_max(const std::vector<int>& xs) { int lo = xs[0]; int hi = xs[0]; for (int x : xs) { if (x < lo) lo = x; if (x > hi) hi = x; } return {lo, hi};}
int main() { auto [lo, hi] = min_max({4, 9, 1, 7}); // structured binding std::cout << lo << " " << hi << "\n";
int only_hi; std::tie(std::ignore, only_hi) = min_max({3, 5}); // std::ignore skips a value std::cout << only_hi << "\n";}1 9 5
What's different
&[i32]is a slice: a borrowed view of some numbers. It accepts a Vec, an array, or part of one. More in the Vectors and Borrowing lessons.- Tuples can hold mixed types:
let t: (i32, &str) = (1, "a");thent.0,t.1. - Swapping needs no temporary variable:
(a, b) = (b, a);. - Don't reach for a tuple to return a value plus an error, as in
(value, err). Rust usesResultfor that (see Errors).
Indexing a tuple like a list
Looks fine: in Python and TypeScript it runs fine. Rust won't compile it.
No direct equivalent in Go. Just a Rust rule.Kotlin refuses this too. Same idea, different words.Works in Python. In Rust it's just a rule.Works in TypeScript. In Rust it's just a rule.No direct equivalent in Java. Just a Rust rule.C# refuses this too. Same idea, different words.No direct equivalent in C. Just a Rust rule.C++ refuses this too. Same idea, different words.fn main() { let t = (3, 4); println!("{}", t[0]);}Go has no tuple type. A function returns several values, and you name each one at the call, so there is nothing to index.
Kotlin refuses too: a Pair has no get operator, so t[0] is an error. You write t.first, or destructure with val (a, b) = t.
fun main() { val t = Pair(3, 4) println(t[0])}Main.kt:3:14: error: no 'get' operator method providing array access.
println(t[0])
^^^t = (3, 4)print(t[0])3
const t: [number, number] = [3, 4];console.log(t[0]);3
Java has no built-in tuple type. A record has accessor methods such as r.lo(), and nothing to index.
C# refuses too (CS0021): a value tuple cannot be indexed. You write t.Item1, or a name you gave the slot.
(int, int) t = (3, 4);Console.WriteLine(t[0]);Program.cs(2,19): error CS0021: Cannot apply indexing with [] to an expression of type '(int, int)'
C has no tuple type. A struct has members you reach with a dot, and an array is indexed with brackets.
C++ refuses too: std::tuple has no operator[]. You write std::get<0>(t), and the index has to be known at compile time (Rust is stricter: a literal).
#include <iostream>#include <tuple>
int main() { std::tuple<int, int> t{3, 4}; std::cout << t[0] << "\n";}main.cpp: In function ‘int main()’:
main.cpp:6:19: error: no match for ‘operator[]’ (operand types are ‘std::tuple<int, int>’ and ‘int’)
6 | std::cout << t[0] << "\n";
| ^What does the compiler say? Guess first, then open.
error[E0608]: cannot index into a value of type `({integer}, {integer})` --> src/main.rs:3:21 | 3 | println!("{}", t[0]); | ^^^ help: to access tuple element `0`, use: `.0` | = help: tuples are indexed with a dot and a literal index: `tuple.0`, `tuple.1`, etc.
Go has no tuple type. A function returns several values, and you name each one at the call, so there is nothing to index.
Kotlin refuses too: a Pair has no get operator, so t[0] is an error. You write t.first, or destructure with val (a, b) = t.
Python indexes a tuple with brackets, and the index can be a variable. It runs and prints 3.
A TypeScript tuple is an array with a fixed length and a type per slot, so t[0] is fine and prints 3.
Java has no built-in tuple type. A record has accessor methods such as r.lo(), and nothing to index.
C# refuses too (CS0021): a value tuple cannot be indexed. You write t.Item1, or a name you gave the slot.
C has no tuple type. A struct has members you reach with a dot, and an array is indexed with brackets.
C++ refuses too: std::tuple has no operator[]. You write std::get<0>(t), and the index has to be known at compile time (Rust is stricter: a literal).
A tuple's slots are fields, numbered from zero, and you reach them with a dot and a literal number: t.0. Square brackets are not defined for a tuple.
Each slot can have its own type. A bracket index whose number is only known when the program runs would have to produce a different type for each number, and the compiler has to know the type of every expression before the program runs. t.0 names the slot in the source, so its type is known. The compiler's help: shows the dot form.
The fix
Compiles and runs.
The toolkit
A tuple groups a fixed number of values (its slots; the std docs say elements), and each slot can have its own type. Rust has no special syntax for returning several values: a function returns one tuple, and the caller unpacks it. Outputs come from Linux on 64-bit ARM with Rust 1.99.1.
A tuple has numbered slots
The type is written like the value: (i32, &str, bool). You read a slot with a dot and a literal number, t.0, t.1 (Declarations and Types).
use std::mem::size_of;
fn main() { let point = (3, 4); println!("point.0: {}, point.1: {}", point.0, point.1);
// Each slot has its own type. let record: (i32, &str, bool) = (7, "seven", true); println!("record: {record:?}"); println!("record.1: {}", record.1);
// Tuples nest, and each .N goes one level in. let nested = ((1, 2), "x"); println!("nested.0.1: {}", nested.0.1);
// A tuple changes in place only if the variable is `mut`. let mut counter = (0, "hits"); counter.0 += 10; println!("counter: {counter:?}");
// size_of::<T>() is the size of T in bytes. println!("size_of::<(i32, i32)>(): {}", size_of::<(i32, i32)>()); println!("size_of::<(u8, u32)>(): {}", size_of::<(u8, u32)>());}point.0: 3, point.1: 4 record: (7, "seven", true) record.1: seven nested.0.1: 2 counter: (10, "hits") size_of::<(i32, i32)>(): 8 size_of::<(u8, u32)>(): 8
The slots sit inside the variable: (i32, i32) is 8 bytes, just the two numbers. (u8, u32) is 8 too, not 5, because the compiler adds padding for alignment and the language lets it reorder slots. The layout of a tuple is not promised; the sizes are what this compiler printed.
A tuple is not a list. t[0] is refused because the slot has to be a literal, so the compiler knows its type. A for loop is refused because the slots can have different types, so there is no single type for x:
fn main() { let t = (3, 4); println!("first: {}", t[0]);}error[E0608]: cannot index into a value of type `({integer}, {integer})` --> src/main.rs:3:28 | 3 | println!("first: {}", t[0]); | ^^^ help: to access tuple element `0`, use: `.0` | = help: tuples are indexed with a dot and a literal index: `tuple.0`, `tuple.1`, etc.
fn main() { let t = (3, 4, 5); for x in t { println!("{x}"); }}error[E0277]: `({integer}, {integer}, {integer})` is not an iterator --> src/main.rs:3:14 | 3 | for x in t { | ^ `({integer}, {integer}, {integer})` is not an iterator | = help: the trait `Iterator` is not implemented for `({integer}, {integer}, {integer})` = note: required for `({integer}, {integer}, {integer})` to implement `IntoIterator`
Tuple (A, B) |
Array [T; N] |
Vec<T> |
|
|---|---|---|---|
| Types of the slots | each its own | all the same | all the same |
| Length | fixed, part of the type | fixed, part of the type | grows |
| Read a slot | t.0, a literal only |
a[i], any usize |
v[i] |
| Loop over it | no | yes | yes |
Arrays and Vec are in Vectors. The rows are run by check-rows.rs.
Returning several values
Declare the return type as a tuple and build it as the tail expression (the last line, with no ;; Statements and Expressions). The caller either unpacks the pair into names or keeps it whole:
fn divmod(a: i32, b: i32) -> (i32, i32) { (a / b, a % b)}
fn main() { // Unpack into two names. let (q, r) = divmod(17, 5); println!("q: {q}, r: {r}");
// Or keep the pair whole and read its slots. let pair = divmod(17, 5); println!("pair: {pair:?}, pair.0: {}", pair.0);
// `_` ignores a slot. let (_, rem) = divmod(40, 7); println!("rem: {rem}");}q: 3, r: 2 pair: (3, 2), pair.0: 3 rem: 5
Unpacking
let (q, r) = ... is a pattern (patterns in let): it has the shape of the value and names its parts (Python calls this unpacking; Kotlin, C# and JavaScript, destructuring). The same shape works in a function’s parameters and in a for loop, and .. skips every slot you do not name. Without .., both sides must have the same number of slots:
// A pattern can sit in a parameter list.fn manhattan((x1, y1): (i32, i32), (x2, y2): (i32, i32)) -> i32 { (x1 - x2).abs() + (y1 - y2).abs()}
fn main() { println!("manhattan: {}", manhattan((1, 2), (4, -2)));
// Nested tuples unpack in one step. let ((a, b), label) = ((1, 2), "pair"); println!("a: {a}, b: {b}, label: {label}");
// `..` skips everything else. let four = (10, 20, 30, 40); let (first, ..) = four; let (.., last) = four; println!("first: {first}, last: {last}");
// A `for` loop takes a pattern too. for (name, score) in [("ana", 3), ("bo", 5)] { println!("{name}: {score}"); }}manhattan: 7 a: 1, b: 2, label: pair first: 10, last: 40 ana: 3 bo: 5
fn main() { let (a, b, c) = (1, 2); println!("{a} {b} {c}");}error[E0308]: mismatched types --> src/main.rs:2:9 | 2 | let (a, b, c) = (1, 2); | ^^^^^^^^^ ------ this expression has type `({integer}, {integer})` | | | expected a tuple with 2 elements, found one with 3 elements | = note: expected tuple `({integer}, {integer})` found tuple `(_, _, _)`
Python raises an error for the same mistake only when the line runs. Rust refuses it while compiling.
Swapping
The right side of (a, b) = (b, a) is built before anything is assigned, so no temporary is needed. This is destructuring assignment: unlike let, it updates variables that already exist, so a variable that already has a value must be mut:
fn main() { let mut a = 1; let mut b = 2; (a, b) = (b, a); println!("after swap: a: {a}, b: {b}");
// Every new value is computed from the old ones, so rotating three works the same way. let (mut x, mut y, mut z) = (1, 2, 3); (x, y, z) = (y, z, x); println!("rotated: x: {x}, y: {y}, z: {z}");}after swap: a: 2, b: 1 rotated: x: 2, y: 3, z: 1
Tuples the standard library hands you
Many std methods return tuples, and you meet them with the same let (a, b) = .... overflowing_add is used in Types:
fn main() { // split_at: two slices from one. let (left, right) = [1, 2, 3, 4].split_at(1); println!("split_at(1): {left:?} and {right:?}");
// overflowing_add: the wrapped sum, and whether it overflowed. let (sum, overflowed) = 250u8.overflowing_add(10); println!("250u8.overflowing_add(10): sum: {sum}, overflowed: {overflowed}");
// Both are plain tuples: keep one whole if you like. let result = 250u8.overflowing_add(10); println!("result.0: {}, result.1: {}", result.0, result.1);}split_at(1): [1] and [2, 3, 4] 250u8.overflowing_add(10): sum: 4, overflowed: true result.0: 4, result.1: true
A missing pair, a failed pair
A pair that might not exist is an Option<(A, B)>, and one that might fail is a Result<(A, B), E>: the tuple goes inside. str::split_once returns the first kind. Option and Errors teach how to open them. The type records that the value may be missing, so you cannot reach it without deciding what to do about None or Err. That is why a Go (value, err) pair is a Result in Rust, not a tuple of two values.
Comparing tuples
Tuples compare slot by slot, left to right: the first slot that differs decides, and later slots only break ties (the std docs say this for PartialOrd and Ord). == compares slot by slot too. That makes a list of tuples sortable, and a tuple a usable map key when its parts are (check-rows.rs runs one):
fn main() { println!("(1, 9) < (2, 0): {}", (1, 9) < (2, 0)); println!("(2, 1) < (2, 3): {}", (2, 1) < (2, 3)); println!("(2, 3) < (2, 3): {}", (2, 3) < (2, 3)); println!("(1, 2) == (1, 2): {}", (1, 2) == (1, 2)); println!("(\"b\", 1) > (\"a\", 9): {}", ("b", 1) > ("a", 9));
// Because they compare, a list of tuples sorts: by the first slot, then the second. let mut results = [(2, "bo"), (1, "zed"), (2, "ana")]; results.sort(); println!("sorted: {results:?}");}(1, 9) < (2, 0): true
(2, 1) < (2, 3): true
(2, 3) < (2, 3): false
(1, 2) == (1, 2): true
("b", 1) > ("a", 9): true
sorted: [(1, "zed"), (2, "ana"), (2, "bo")]Tuple or struct?
A tuple does not say what its slots mean. When both slots have the same type, the compiler cannot catch a swapped pair:
// With a tuple, nothing says which slot is which.fn aspect(size: (u32, u32)) -> u32 { size.0 / size.1}
// A struct names them (the Structs lesson covers this).struct Size { width: u32, height: u32,}
fn aspect_named(size: Size) -> u32 { size.width / size.height}
fn main() { // Width first, height second? The compiler cannot tell this is swapped. println!("aspect((16, 9)): {}", aspect((16, 9))); println!("aspect((9, 16)): {}", aspect((9, 16)));
let size = Size { width: 16, height: 9, }; println!("aspect_named: {}", aspect_named(size));}aspect((16, 9)): 1 aspect((9, 16)): 0 aspect_named: 1
aspect((9, 16)) compiles and gives a different answer. A struct names the fields, so a caller writes width: 16 and cannot mix them up.
| Reach for a tuple when | Reach for a struct when |
|---|---|
| the result is short-lived and the caller unpacks it at once | the value is stored, passed around or returned from a public API |
| the slots have different types, so mixing them up does not compile | two slots share a type, or the slots keep growing in number |
Your language, in Rust
Going deeper
The empty tuple and the one-slot tuple
() is the tuple with no slots, called unit (the value for “nothing”); Result<(), E> means “it worked, there is nothing to hand back”. A one-slot tuple needs a comma, because (5) is just 5 in parentheses:
use std::mem::size_of;
fn log(msg: &str) { println!("log: {msg}");}
fn check(ok: bool) -> Result<(), String> { if ok { Ok(()) } else { Err("not ok".to_string()) }}
fn main() { // A function with no `->` returns the empty tuple. let nothing = log("hello"); println!("nothing: {nothing:?}"); println!("size_of::<()>(): {}", size_of::<()>());
// `Result<(), E>`: "it worked, there is nothing to return". println!("check(true): {:?}", check(true)); println!("check(false): {:?}", check(false));
// A one-slot tuple needs the comma. let one = (5,); println!("one: {one:?}, one.0: {}", one.0);}log: hello
nothing: ()
size_of::<()>(): 0
check(true): Ok(())
check(false): Err("not ok")
one: (5,), one.0: 5fn main() { let t: (i32,) = (5); println!("{}", t.0);}error[E0308]: mismatched types --> src/main.rs:2:21 | 2 | let t: (i32,) = (5); | ------ ^^^ expected `(i32,)`, found integer | | | expected due to this | = note: expected tuple `(i32,)` found type `{integer}` help: use a trailing comma to create a tuple with one element | 2 | let t: (i32,) = (5,); | +
Tuples stop at twelve, for the standard traits
Any length is a legal tuple type. But the traits that give a tuple {:?} printing, ==, ordering, a default value and hashing (Debug, PartialEq, PartialOrd, Ord, Eq, Default, Hash; see Traits) are implemented only up to 12 slots. The std docs call it a “temporary restriction in Rust’s type system” that “may change”. Clone and Copy have no limit:
fn main() { let twelve = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); println!("twelve: {twelve:?}");
// A 13-slot tuple is a fine type: you can build it, read a slot and copy it... let thirteen = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); let copy = thirteen; println!("thirteen.12: {}, copy.12: {}", thirteen.12, copy.12); // ...but `{:?}` on it is refused (see below).}twelve: (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) thirteen.12: 13, copy.12: 13
fn main() { let thirteen = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); println!("thirteen.12: {}", thirteen.12); println!("thirteen: {thirteen:?}");}error[E0277]: `({integer}, {integer}, {integer}, {integer}, {integer}, {integer}, {integer}, {integer}, {integer}, {integer}, {integer}, {integer}, {integer})` doesn't implement `Debug` --> src/main.rs:4:25 | 4 | println!("thirteen: {thirteen:?}"); | ^^^^^^^^^^^^ `({integer}, {integer}, {integer}, {integer}, {integer}, {integer}, {integer}, {integer}, {integer}, {integer}, {integer}, {integer}, {integer})` cannot be formatted using `{:?}` because it doesn't implement `Debug` | = help: the trait `Debug` is not implemented for `({integer}, {integer}, {integer}, {integer}, {integer}, {integer}, {integer}, {integer}, {integer}, {integer}, {integer}, {integer}, {integer})` = help: the following other types implement trait `Debug`: (T₁, T₂, …, Tₙ) up to tuples of arity 12 and 5 others
Practice
Try it
Open each exercise in the Playground, replace the todo!(), and run it until it prints all checks passed.
Two Fibonacci numbers at once
Return (F(n), F(n + 1)) as a (u64, u64). Keep the pair in two mut variables and update both at once with a destructuring assignment. The checks go as far as F(93), the largest Fibonacci number a u64 holds.
/// Returns (F(n), F(n + 1)), where F(0) = 0 and F(1) = 1.fn fib_pair(n: u32) -> (u64, u64) { todo!("start from (0, 1); update both numbers at once, n times")}
fn main() { assert_eq!(fib_pair(0), (0, 1)); assert_eq!(fib_pair(1), (1, 1)); assert_eq!(fib_pair(10), (55, 89)); // F(93) is the largest Fibonacci number that fits in a u64. assert_eq!(fib_pair(92), (7540113804746346429, 12200160415121876738));
// The caller unpacks the pair. let (this, next) = fib_pair(20); assert_eq!(next - this, 4181);
println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
/// Returns (F(n), F(n + 1)), where F(0) = 0 and F(1) = 1.fn fib_pair(n: u32) -> (u64, u64) { let (mut a, mut b) = (0, 1); for _ in 0..n { (a, b) = (b, a + b); } (a, b)}
fn main() { assert_eq!(fib_pair(0), (0, 1)); assert_eq!(fib_pair(1), (1, 1)); assert_eq!(fib_pair(10), (55, 89)); // F(93) is the largest Fibonacci number that fits in a u64. assert_eq!(fib_pair(92), (7540113804746346429, 12200160415121876738));
// The caller unpacks the pair. let (this, next) = fib_pair(20); assert_eq!(next - this, 4181);
println!("all checks passed");}all checks passed
The smallest rectangle around some points
Return the bottom-left and top-right corners of a slice of (i32, i32) points, as a tuple of tuples. A check puts the smallest x and the smallest y on different points, and the caller unpacks the result with a nested pattern.
/// The smallest rectangle that holds every point, as (bottom_left, top_right)./// `points` is never empty.fn bounding_box(points: &[(i32, i32)]) -> ((i32, i32), (i32, i32)) { todo!("start both corners at points[0]; `for &(x, y) in points` unpacks each point")}
fn main() { assert_eq!(bounding_box(&[(1, 2)]), ((1, 2), (1, 2))); assert_eq!(bounding_box(&[(4, 4), (4, 4)]), ((4, 4), (4, 4))); // Points on a line. assert_eq!(bounding_box(&[(0, 5), (3, 5)]), ((0, 5), (3, 5))); // The smallest x and the smallest y come from different points. assert_eq!( bounding_box(&[(3, -1), (-2, 5), (0, 0)]), ((-2, -1), (3, 5)) );
// The caller unpacks a tuple of tuples in one step. let ((x0, y0), (x1, y1)) = bounding_box(&[(0, 0), (6, 4)]); assert_eq!((x1 - x0) * (y1 - y0), 24);
println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
/// The smallest rectangle that holds every point, as (bottom_left, top_right)./// `points` is never empty.fn bounding_box(points: &[(i32, i32)]) -> ((i32, i32), (i32, i32)) { let (mut x0, mut y0) = points[0]; let (mut x1, mut y1) = points[0]; for &(x, y) in points { if x < x0 { x0 = x; } if y < y0 { y0 = y; } if x > x1 { x1 = x; } if y > y1 { y1 = y; } } ((x0, y0), (x1, y1))}
fn main() { assert_eq!(bounding_box(&[(1, 2)]), ((1, 2), (1, 2))); assert_eq!(bounding_box(&[(4, 4), (4, 4)]), ((4, 4), (4, 4))); // Points on a line. assert_eq!(bounding_box(&[(0, 5), (3, 5)]), ((0, 5), (3, 5))); // The smallest x and the smallest y come from different points. assert_eq!( bounding_box(&[(3, -1), (-2, 5), (0, 0)]), ((-2, -1), (3, 5)) );
// The caller unpacks a tuple of tuples in one step. let ((x0, y0), (x1, y1)) = bounding_box(&[(0, 0), (6, 4)]); assert_eq!((x1 - x0) * (y1 - y0), 24);
println!("all checks passed");}all checks passed
Read more
- The Rust Book, chapter 3.2: Data Types, the tuple type, and chapter 19.3: Pattern Syntax for destructuring.
- Rust by Example: Tuples.
- The
tupleprimitive type in the std docs, with the list of traits and the 12-slot limit, and Tuple types and Type layout in the Reference. - Methods used above:
str::split_once,slice::split_at,u8::overflowing_add.
Learn Rust is an independent project, not affiliated with the Rust Project or the Rust Foundation. Code and text are under the MIT licence.Source on GitHub.