---
title: "18.7 Arrays and slices, revisited"
description: "Fixed-size arrays [T; N] in depth, when a fixed length wins, slices as the universal parameter type that accepts arrays and vectors alike, and nested data for 2D grids."
url: "https://learnrust.net/chapter-18/arrays-and-slices-revisited/"
last_updated: "2026-06-13"
---

# 18.7 Arrays and slices, revisited

You've met fixed-size arrays (lesson [4.x](https://learnrust.net/chapter-4/chapter-4-summary-and-quiz/)) and slices (lesson [9.6](https://learnrust.net/chapter-9/slices/)) in passing. Now that `Vec` is in hand, this lesson draws the full picture: when a fixed-length array beats a growable `Vec`, why **slices** are the parameter type that lets one function accept both, and how to build the nested structures you'd use for a grid. This absorbs a great deal of C++'s array material into one lesson, because Rust has no decaying C arrays to agonize over.

## Arrays: fixed length, known at compile time

An array `[T; N]` holds exactly `N` values of type `T`, with `N` fixed at compile time. Its length is part of its type, `[i32; 3]` and `[i32; 4]` are *different types*, and because the size is known, an array can live entirely on the stack (lesson [8.2](https://learnrust.net/chapter-8/the-stack-and-the-heap/)), no heap allocation:

```rust
fn main() {
    let week = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
    let zeros = [0; 5];                  // [0, 0, 0, 0, 0]: five zeros

    println!("{}", week[0]);
    println!("{}", week.len());          // 7
    println!("{zeros:?}");
}
```

```
Mon
7
[0, 0, 0, 0, 0]
```

`[0; 5]` is the shorthand for "five copies of 0." Arrays are bounds-checked just like vectors: `week[10]` panics, `week.get(10)` returns `None` (lesson [18.2](https://learnrust.net/chapter-18/vec-creating-and-updating/)). The defining trait is the fixed length: you cannot `push` to an array, because its size can never change. Use an array when the count is genuinely fixed and known up front, the 7 days of a week, the 3 RGB channels of a color, a chess board's 64 squares, and you get stack allocation and a compile-time-known size for free. When the count varies, use a `Vec`.

## Slices: a view into either

Here's the unifying concept. A **slice** `&[T]` is a borrowed view into a contiguous run of elements (lesson [9.6](https://learnrust.net/chapter-9/slices/)), and crucially it doesn't care whether those elements live in an array or a `Vec`. Both can be *viewed* as a slice, which makes `&[T]` the ideal parameter type for a function that just wants to read a sequence:

```rust
fn sum(numbers: &[i32]) -> i32 {
    let mut total = 0;
    for n in numbers {
        total += n;
    }
    total
}

fn main() {
    let array = [1, 2, 3];
    let vector = vec![4, 5, 6];

    println!("{}", sum(&array));     // array works
    println!("{}", sum(&vector));    // vec works too
}
```

```
6
15
```

`sum` takes `&[i32]`, a slice, and accepts both `&array` and `&vector`. A `&[i32; 3]` (array reference) and a `&Vec<i32>` both coerce to `&[i32]` automatically, the same deref-coercion convenience from lesson [9.7](https://learnrust.net/chapter-9/string-slices/) that lets `&String` pass where `&str` is wanted. So you write the function once against the slice and call it with arrays or vectors interchangeably. This is exactly why text functions take `&str` rather than `&String`: `&str` is a slice of string, `&[T]` is a slice of anything, and slices are the universal "I just need to read a sequence" parameter.

> **Best practice**
>
> Write functions to take `&[T]` (a slice), not `&Vec<T>` or `&[T; N]` (a specific container). The slice accepts arrays, vectors, and sub-ranges of either, so your function works for every caller without caring how they store their data. Take `&Vec<T>` only when you specifically need vector-only operations. This is the lesson [9.6](https://learnrust.net/chapter-9/slices/) advice generalized: borrow the view, not the container.

## Nested collections for 2D data

For a grid or table, you nest collections: a `Vec` of `Vec`s (dynamic in both dimensions) or an array of arrays (fixed grid):

```rust
fn main() {
    let grid = vec![
        vec![1, 2, 3],
        vec![4, 5, 6],
    ];

    println!("{}", grid[1][2]);      // row 1, column 2 → 6

    for row in &grid {
        for cell in row {
            print!("{cell} ");
        }
        println!();
    }
}
```

```
6
1 2 3 
4 5 6 
```

`grid[1][2]` indexes row 1, then column 2 within that row. A `Vec<Vec<i32>>` lets rows and columns each grow; a fixed `[[i32; 3]; 2]` (an array of two 3-element arrays) is a stack-allocated fixed grid. Nesting is how you represent any multi-dimensional data, and it's the same `Vec`/array choice at each level: dynamic dimensions use `Vec`, fixed dimensions use arrays. This is all Rust needs for the multidimensional data that C++ handles with raw 2D arrays and pointer arithmetic.

## The C array material that isn't here

Worth noting what this lesson *doesn't* contain, because it's a chunk of a C++ course that simply has no Rust equivalent. There are no decaying arrays (a Rust array doesn't silently turn into a pointer and lose its length), no separate length to pass alongside a pointer (a slice carries its own length), and no manual bounds management (every access is checked). The whole category of C-array footguns, off-by-one buffer overruns, lost lengths, pointer arithmetic mistakes, is gone, replaced by arrays that know their size and slices that carry their length. That collapse is why this is one lesson where a C++ course needs many.

## Quiz time

**Question #1**

What's the key difference between an array `[i32; 3]` and a `Vec<i32>`, and when do you choose each?

<details class="solution">
<summary>Show solution</summary>

An array has a fixed length known at compile time (part of its type) and can live on the stack; a `Vec` has a runtime-varying length and lives on the heap. Choose an array when the count is genuinely fixed and known up front (days of the week, RGB channels); choose a `Vec` when the count varies or isn't known until runtime, which is most of the time. You can't `push` to an array.

</details>

**Question #2**

Why should a function that reads a sequence take `&[T]` rather than `&Vec<T>`?

<details class="solution">
<summary>Show solution</summary>

Because `&[T]` (a slice) is a view that both arrays and vectors coerce to, so the function accepts `&array`, `&vector`, and sub-ranges of either, working for every caller regardless of how they store the data. `&Vec<T>` would accept only vectors. Taking the slice is the general, flexible choice, the same reason text functions take `&str` over `&String` (lesson [9.6](https://learnrust.net/chapter-9/slices/)).

</details>

**Question #3**

How would you represent a 2D grid whose dimensions can grow, and how do you index a cell?

<details class="solution">
<summary>Show solution</summary>

A `Vec<Vec<T>>`: a vector of rows, each row a vector of cells, so both dimensions can grow. Index a cell with `grid[row][col]` (first the row, then the column within it). For a fixed-size grid you'd use nested arrays like `[[T; COLS]; ROWS]` instead, stack-allocated with fixed dimensions.

</details>

You've now seen `Vec`, `String`, `HashMap`, arrays, and slices. The standard library has a few more collections for special cases. The last lesson is a decision guide: when to reach for `VecDeque`, `HashSet`, or `BTreeMap` instead of the core three.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
