Lesson 9 of 94
Tuples & Arrays
Rust has two built-in ways to group several values into one: tuples, which hold a fixed number of values of possibly different types, and arrays, which hold a fixed number of values of the same type.
| Concept | Rust |
|---|---|
| A small group of mixed values | Tuple (i32, &str) |
| Fixed-size array | let a = [0; 5]; with type [i32; 5] |
| Length | a.len() |
| Bad index | Compile error or panic |
| Assignment | Arrays are values: assignment copies them |
A growable list is a Vec, which you meet in the next lesson.
Part 1: Tuples
1. Creating a tuple
A tuple is written as values in parentheses. Tuples cannot be printed with {}, so use the Debug format {:?}.
fn main() {
let point = (3, 7);
let product = ("Keyboard", 49.99, true);
println!("{:?}", point);
println!("{:?}", product);
}(3, 7)
("Keyboard", 49.99, true)2. The tuple's type
The type of a tuple lists the type of each element. You can write it out, just like any other type annotation.
fn main() {
let employee: (u32, &str, f64) = (1042, "Grace", 8_250.0);
println!("{:?}", employee);
}(1042, "Grace", 8250.0)3. Accessing elements by position
Use .0, .1, .2, and so on. Positions start at zero.
fn main() {
let employee = (1042, "Grace", 8_250.0);
println!("id: {}", employee.0);
println!("name: {}", employee.1);
println!("salary: {}", employee.2);
}id: 1042
name: Grace
salary: 82504. Destructuring a tuple
A let can take a tuple apart into separate variables in one step. This is called destructuring.
fn main() {
let employee = (1042, "Grace", 8_250.0);
let (id, name, salary) = employee;
println!("{name} (#{id}) earns {salary}");
}Grace (#1042) earns 82505. Ignoring parts with _
fn main() {
let http_response = (200, "OK", "<html>...</html>");
let (status, _, _) = http_response;
let (_, reason, ..) = http_response; // `..` ignores all the rest
println!("{status} {reason}");
}200 OK6. Returning several values from a function
This is the most common use of tuples.
fn min_max(a: i32, b: i32, c: i32) -> (i32, i32) {
let min = a.min(b).min(c);
let max = a.max(b).max(c);
(min, max)
}
fn main() {
let (lowest, highest) = min_max(17, -4, 9);
println!("min={lowest}, max={highest}");
}min=-4, max=177. Tuples as parameters
fn distance(from: (f64, f64), to: (f64, f64)) -> f64 {
let dx = to.0 - from.0;
let dy = to.1 - from.1;
(dx * dx + dy * dy).sqrt()
}
fn main() {
let home = (0.0, 0.0);
let office = (3.0, 4.0);
println!("distance: {}", distance(home, office));
}distance: 58. Destructuring in parameters
A parameter can itself be a pattern, which destructures the tuple as it arrives.
fn describe((width, height): (u32, u32)) -> String {
format!("{width}x{height} ({} pixels)", width * height)
}
fn main() {
println!("{}", describe((1920, 1080)));
}1920x1080 (2073600 pixels)9. Swapping values
Destructuring assignment swaps two variables without a temporary variable.
fn main() {
let mut first = "left";
let mut second = "right";
(first, second) = (second, first);
println!("{first} {second}");
}right left10. Mutable tuples
fn main() {
let mut position = (0, 0);
position.0 += 5;
position.1 -= 2;
println!("{:?}", position);
}(5, -2)11. Comparing tuples
Tuples with comparable elements can be compared with == and <. Comparison is lexicographic: the first elements are compared first, and later elements only break ties. This is a handy way to compare versions.
fn main() {
let installed = (1, 9, 4);
let required = (1, 10, 0);
println!("equal? {}", installed == required);
println!("outdated? {}", installed < required);
println!("{}", (2, 0) > (1, 99));
}equal? false
outdated? true
true12. Nested tuples
fn main() {
let route = (("Lisbon", 38.72), ("Porto", 41.15));
let ((from, from_lat), (to, to_lat)) = route;
println!("{from} ({from_lat}) -> {to} ({to_lat})");
println!("{}", (route.1).0);
}Lisbon (38.72) -> Porto (41.15)
Porto13. The empty tuple ()
The tuple with no elements, (), is the unit type from Functions. It is the value of expressions that produce "nothing".
fn main() {
let nothing = ();
println!("{:?}", nothing);
}()Part 2: Arrays
14. Creating an array
An array holds a fixed number of values of the same type. Its type is written [Type; length], and the length is part of the type: [i32; 3] and [i32; 4] are different types.
fn main() {
let scores: [i32; 5] = [88, 92, 75, 64, 99];
let weekdays = ["Mon", "Tue", "Wed", "Thu", "Fri"];
println!("{:?}", scores);
println!("{:?}", weekdays);
}[88, 92, 75, 64, 99]
["Mon", "Tue", "Wed", "Thu", "Fri"]15. Arrays with a repeated value
[value; count] creates an array filled with the same value.
fn main() {
let zeros = [0; 8];
let seats = [true; 4];
println!("{:?}", zeros);
println!("{:?}", seats);
}[0, 0, 0, 0, 0, 0, 0, 0]
[true, true, true, true]16. Indexing and length
Indexes start at 0 and must be usize values.
fn main() {
let months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"];
println!("first: {}", months[0]);
println!("last: {}", months[months.len() - 1]);
println!("count: {}", months.len());
}first: Jan
last: Jun
count: 617. An out-of-bounds index the compiler can see
If the index is known at compile time and is too large, the program does not compile.
fn main() {
let months = ["Jan", "Feb", "Mar"];
println!("{}", months[5]);
}error: this operation will panic at runtime
--> src/main.rs:3:20
|
3 | println!("{}", months[5]);
| ^^^^^^^^^ index out of bounds: the length is 3 but the index is 5
|
= note: `#[deny(unconditional_panic)]` on by default18. An out-of-bounds index at runtime panics
If the index is only known at runtime, a bad index panics with a clear message. It never reads random memory, the classic C bug.
fn month_name(number: usize) -> &'static str {
let months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
months[number - 1]
}
fn main() {
println!("{}", month_name(9));
println!("{}", month_name(13));
}Sep
thread 'main' panicked at src/main.rs:4:5:
index out of bounds: the len is 12 but the index is 12To get a value safely, months.get(i) returns an Option instead of panicking. See Option.
19. Looping over an array
A for loop visits each element in order.
fn main() {
let prices = [4.50, 12.99, 3.25];
let mut total = 0.0;
for price in prices {
total += price;
}
println!("total: {total:.2}");
}total: 20.7420. Looping with the index
Two ways: loop over the index range, or use .iter().enumerate(), which yields (index, element) tuples. The second is more idiomatic and avoids any chance of an index mistake. Iterators have their own lesson.
fn main() {
let podium = ["Ana", "Ben", "Chloe"];
for i in 0..podium.len() {
println!("#{} {}", i + 1, podium[i]);
}
for (i, name) in podium.iter().enumerate() {
println!("place {}: {name}", i + 1);
}
}#1 Ana
#2 Ben
#3 Chloe
place 1: Ana
place 2: Ben
place 3: Chloe21. Changing elements
The whole array must be mut to change any element.
fn main() {
let mut stock = [10, 0, 7];
stock[1] = 25;
stock[2] -= 1;
println!("{:?}", stock);
}[10, 25, 6]22. Arrays are values, not references
A Rust array is a plain value: assigning it copies all the elements.
fn main() {
let original = [1, 2, 3];
let mut copy = original;
copy[0] = 100;
println!("original: {:?}", original);
println!("copy: {:?}", copy);
}original: [1, 2, 3]
copy: [100, 2, 3]The same happens when you pass an array to a function: the function gets its own copy. Arrays of simple types are copied, like the numbers inside them. The exact rule, the Copy trait, is covered in Move, Copy & Clone.
23. The length is part of the type
A function that takes [i32; 3] only accepts arrays of exactly three elements.
fn average(values: [i32; 3]) -> i32 {
(values[0] + values[1] + values[2]) / 3
}
fn main() {
let readings = [10, 20, 30, 40];
println!("{}", average(readings));
}error[E0308]: mismatched types
--> src/main.rs:7:28
|
7 | println!("{}", average(readings));
| ------- ^^^^^^^^ expected an array with a size of 3, found one with a size of 4
| |
| arguments to this function are incorrect
|
note: function defined here
--> src/main.rs:1:4
|
1 | fn average(values: [i32; 3]) -> i32 {
| ^^^^^^^ ----------------To accept arrays of any length, functions take a slice &[i32] instead. Slices are introduced in Slices, and this limitation is why most functions use them.
24. Comparing and searching arrays
fn main() {
let expected = [1, 2, 3];
let actual = [1, 2, 3];
println!("equal: {}", expected == actual);
let banned_ports = [23, 135, 445];
let requested = 445;
println!("port {requested} banned: {}", banned_ports.contains(&requested));
}equal: true
port 445 banned: true25. Sorting and reversing an array
fn main() {
let mut response_times_ms = [120, 45, 300, 87, 45, 210];
response_times_ms.sort();
println!("sorted: {:?}", response_times_ms);
println!("median: {}", response_times_ms[response_times_ms.len() / 2]);
response_times_ms.reverse();
println!("slowest first: {:?}", response_times_ms);
}sorted: [45, 45, 87, 120, 210, 300]
median: 120
slowest first: [300, 210, 120, 87, 45, 45]26. Two-dimensional arrays
An array of arrays forms a grid. The type [[i32; 3]; 2] means "2 rows of 3 columns".
fn main() {
let mut grid: [[i32; 3]; 2] = [[1, 2, 3], [4, 5, 6]];
grid[1][2] = 60;
for row in grid {
for cell in row {
print!("{cell:4}");
}
println!();
}
} 1 2 3
4 5 6027. Realistic example: weekly temperature statistics
fn main() {
let days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
let highs = [18.5, 21.0, 19.2, 24.8, 26.1, 23.4, 17.9];
let mut warmest = 0;
let mut coldest = 0;
let mut sum = 0.0;
for i in 0..highs.len() {
sum += highs[i];
if highs[i] > highs[warmest] {
warmest = i;
}
if highs[i] < highs[coldest] {
coldest = i;
}
}
let average = sum / highs.len() as f64;
println!("Warmest: {} ({}°C)", days[warmest], highs[warmest]);
println!("Coldest: {} ({}°C)", days[coldest], highs[coldest]);
println!("Average: {average:.1}°C");
}Warmest: Fri (26.1°C)
Coldest: Sun (17.9°C)
Average: 21.6°C28. Realistic example: checking a tic-tac-toe winner
A 3x3 grid of characters, plus an array of the 8 winning lines. Each line is an array of three (row, column) tuples.
fn winner(board: [[char; 3]; 3]) -> char {
let lines = [
[(0, 0), (0, 1), (0, 2)], [(1, 0), (1, 1), (1, 2)], [(2, 0), (2, 1), (2, 2)],
[(0, 0), (1, 0), (2, 0)], [(0, 1), (1, 1), (2, 1)], [(0, 2), (1, 2), (2, 2)],
[(0, 0), (1, 1), (2, 2)], [(0, 2), (1, 1), (2, 0)],
];
for [(r1, c1), (r2, c2), (r3, c3)] in lines {
let first = board[r1][c1];
if first != '.' && first == board[r2][c2] && first == board[r3][c3] {
return first;
}
}
'-'
}
fn main() {
let board = [
['X', 'O', 'X'],
['O', 'X', 'O'],
['.', 'O', 'X'],
];
for row in board {
println!("{} {} {}", row[0], row[1], row[2]);
}
println!("winner: {}", winner(board));
}X O X
O X O
. O X
winner: XNotice the for loop destructures each line into three tuples in one pattern: [(r1, c1), (r2, c2), (r3, c3)]. Patterns can take apart arrays as well as tuples, and you will see much more of this in Pattern Matching.
29. Realistic example: a monthly sales report
fn main() {
let regions = ["North", "South", "East", "West"];
// rows: regions, columns: Jan, Feb, Mar
let sales: [[u32; 3]; 4] = [
[120, 135, 150],
[98, 87, 110],
[143, 160, 171],
[75, 90, 64],
];
println!("{:<8}{:>6}{:>6}{:>6}{:>8}", "Region", "Jan", "Feb", "Mar", "Total");
let mut month_totals = [0; 3];
for (r, region) in regions.iter().enumerate() {
let mut total = 0;
print!("{region:<8}");
for month in 0..3 {
print!("{:>6}", sales[r][month]);
total += sales[r][month];
month_totals[month] += sales[r][month];
}
println!("{total:>8}");
}
let grand: u32 = month_totals[0] + month_totals[1] + month_totals[2];
println!("{:<8}{:>6}{:>6}{:>6}{:>8}", "All", month_totals[0], month_totals[1], month_totals[2], grand);
}Region Jan Feb Mar Total
North 120 135 150 405
South 98 87 110 295
East 143 160 171 474
West 75 90 64 229
All 436 472 495 1403Summary
- A tuple groups a fixed number of values of any types:
(i32, &str, bool). Access with.0,.1, or destructure withlet (a, b) = t;. - Tuples are the lightweight way to return several values from a function.
- An array holds a fixed number of values of one type:
[i32; 5]. The length is part of its type. - Indexing is bounds-checked: a compile error or a panic, never undefined behavior.
- Arrays are values. Assigning or passing one copies it.
- For a list that can grow, use
Vec, coming up next.
