RustArena

Step 6 of 6

Const generics: arrays of any length

An array's length is part of its type: [f64; 7] and [f64; 2] are different types. So how do you write one function for arrays of every length? With a const generic parameter: a parameter that stands for a value known at compile time, instead of a type.

fn average<const N: usize>(readings: [f64; N]) -> f64 {
    let mut total = 0.0;
    for r in readings {
        total += r;
    }
    total / N as f64
}

fn main() {
    let week = [12.0, 14.5, 13.0, 15.5, 16.0, 11.0, 12.5];
    let weekend = [20.0, 22.0];
    println!("week: {:.2}", average(week));
    println!("weekend: {:.2}", average(weekend));
}
week: 13.50
weekend: 21.00

Const parameters work on structs too, mixed freely with type parameters. Here's a game board whose size is part of its type:

struct Grid<const W: usize, const H: usize> {
    cells: [[char; W]; H],
}

impl<const W: usize, const H: usize> Grid<W, H> {
    fn new() -> Self {
        Grid { cells: [['.'; W]; H] }
    }

    fn set(&mut self, x: usize, y: usize, c: char) {
        self.cells[y][x] = c;
    }

    fn print(&self) {
        for row in &self.cells {
            let mut line = String::new();
            for c in row {
                line.push(*c);
            }
            println!("{line}");
        }
    }
}

fn main() {
    let mut board: Grid<5, 3> = Grid::new();
    board.set(0, 0, '@');
    board.set(4, 2, '
#39;); board.print(); }
@....
.....
....$

[['.'; W]; H] builds an array of H rows, each an array of W dots. impl<const W: usize, const H: usize> declares the parameters for the impl block, exactly like impl<T>.

Const generics or slices?

For "a list of things", a slice &[T] is usually the right parameter: it accepts arrays, Vecs and parts of them, of any length decided at run time. Reach for const generics when the length is part of the meaning and the compiler should enforce it:

The standard library itself uses const generics: they're why {:?}, == and many other features work on arrays of any length.

Common mistakes

fn brightness(color: [u8; 3]) -> u32 {
    color[0] as u32 + color[1] as u32 + color[2] as u32
}

fn main() {
    println!("{}", brightness([255, 128, 0, 255]));
}
error[E0308]: mismatched types
  |     println!("{}", brightness([255, 128, 0, 255]));
  |                    ---------- ^^^^^^^^^^^^^^^^^^ expected an array with a size of 3, found one with a size of 4

Your task

The dot product of two vectors multiplies matching elements and adds the results: [1, 2, 3] and [4, 5, 6] give 1*4 + 2*5 + 3*6 = 32. The starter only works for length 3. Make dot generic over the length, so the compiler guarantees both arrays have the same length, whatever it is.

Challenge 2

Write fn reversed<T: Copy, const N: usize>(arr: [T; N]) -> [T; N] that returns a reversed copy of the array. Don't use .reverse(): make a mutable copy (let mut out = arr;, allowed because T: Copy makes the array Copy too) and swap elements with out.swap(i, j).

Challenge 3

A Matrix<R, C> has R rows and C columns, both const parameters. Write the transpose method, which turns rows into columns: the result is a Matrix<C, R>, and the element at row r, column c moves to row c, column r. The type system guarantees the new shape.

You'll need to start from a filled array of the new shape: [[0; R]; C].

Previous: Monomorphization: generics cost nothing at run time