Rust Const Generics
A const generic is a compile-time value that acts as a parameter. An array carries its length this way, so [i32; 3] and [i32; 4] are different types.
A size in the type
ROWS and COLS are numbers, not types. Grid<i32, 2, 3> holds a [[i32; 3]; 2] — two rows of three. Its count is worked out from the parameters alone:
struct Grid<T, const ROWS: usize, const COLS: usize> {
cells: [[T; COLS]; ROWS],
}One function, every size
total never names a size. The N in its signature is read off each array it is handed:
fn total<const N: usize>(arr: [i32; N]) -> i32 {
let mut sum = 0;
for n in arr {
sum += n;
}
sum
}
println!("total of 3 = {}", total([1, 2, 3]));
println!("total of 4 = {}", total([10, 20, 30, 40]));Different sizes, different types
The size is part of the type, so a three-slot array can never slip into a four-slot place:
let three: [i32; 3] = [1, 2, 3];
let four: [i32; 4] = three; // error E0308: mismatched typesA bool can be a parameter too
The parameter need not be a size. Here a bool picks which impl exists. Meter<true> measures in metres. Meter<false> measures in feet. Each value gets only its own unit method:
struct Meter<const METRIC: bool>;
impl Meter<true> {
fn unit(&self) -> &str { "metres" }
}
impl Meter<false> {
fn unit(&self) -> &str { "feet" }
}
println!("metric unit = {}", Meter::<true>.unit());
println!("other unit = {}", Meter::<false>.unit());Arithmetic on N is not allowed yet
A size built by calculating with N — growing an array by one — is beyond stable Rust today. The compiler refuses it with the message below. The working pattern is a second const parameter for the second size:
fn grow<const N: usize>(arr: [u8; N]) -> [u8; N + 1] {
// refused on stable Rust:
// error: generic parameters may not be used in const operations
}Example
struct Grid<T, const ROWS: usize, const COLS: usize> {
cells: [[T; COLS]; ROWS],
}
impl<T: Copy + Default, const ROWS: usize, const COLS: usize> Grid<T, ROWS, COLS> {
fn new() -> Grid<T, ROWS, COLS> {
Grid { cells: [[T::default(); COLS]; ROWS] }
}
fn count(&self) -> usize {
ROWS * COLS
}
fn set(&mut self, row: usize, col: usize, value: T) {
self.cells[row][col] = value;
}
fn get(&self, row: usize, col: usize) -> T {
self.cells[row][col]
}
}
fn first<const N: usize>(arr: [i32; N]) -> i32 {
arr[0]
}
fn total<const N: usize>(arr: [i32; N]) -> i32 {
let mut sum = 0;
for n in arr {
sum += n;
}
sum
}
fn main() {
let mut board: Grid<i32, 2, 3> = Grid::new();
board.set(1, 2, 42);
println!("cells = {}", board.count());
println!("corner = {}", board.get(1, 2));
println!("first of 3 = {}", first([7, 8, 9]));
println!("first of 5 = {}", first([4, 5, 6, 7, 8]));
println!("total of 3 = {}", total([1, 2, 3]));
println!("total of 4 = {}", total([10, 20, 30, 40]));
}Key points
- Write const N: usize in the angle brackets.
- That accepts a compile-time number as a parameter: struct Grid<T, const N: usize>
- A const parameter may be an integer type, bool or char — values the compiler can hold while it works
- Arrays of different sizes are different types.
- Handing a [i32; 3] where a [i32; 4] is wanted fails with error E0308
- A function generic over N works for every size at once.
- total adds a [i32; 3] and a [i32; 40] with the same body
- Use a plain &[T] parameter when the size does not matter: a slice accepts an array of any size
- Arithmetic on a const parameter inside a type is refused on stable Rust.
- The refused shape is writing [u8; N + 1].
- Generic parameters may not be used in const operations.
- Pass the second size as its own const parameter instead
- Grid<i32, 2, 3> and Grid<i32, 4, 4> are simply two types, like any others.
In this example, Grid is generic over a type and over two numbers. board is a Grid<i32, 2, 3>, so count multiplies the parameters: 2 times 3 prints cells = 6. The set call stores 42 in the last cell. get reads it back as corner = 42. Below, first and total each take any array. The compiler reads N from the argument. The three-item array gives first of 3 = 7. The five-item array gives first of 5 = 4. total adds both sizes with one body: 6 and 100. Nothing about the sizes was decided while the program ran — every N was settled by the compiler.
Note
A const parameter is settled while compiling and costs nothing while running.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
What kind of parameter is the N in struct Grid<T, const N: usize>?