For many purposes, expecially in low-level programming, fixed-size arrays are useful. This is a simple function that zips two arrays, it enforces at compile-time the equality of the length of the two arrays:
fn zip_array1<const N: usize, 'a, T, U>(a: &'a [T; N], b: &'a [U; N]) -> impl Iterator<Item=(&'a T, &'a U)> {
// Zip implementation
a.iter().zip(b)
}
A disadvantage is the code bloat, because every different N gets monomorphized, to avoid most of that you can use an auxiliary function that performs erasure on the array lengths information, turning the lengths in run-time values in the slices:
fn zip_array2<const N: usize, 'a, T, U>(a: &'a [T; N], b: &'a [U; N]) -> impl Iterator<Item=(&'a T, &'a U)> {
fn helper<'a, T, U>(a: &'a [T], b: &'a [U]) -> impl Iterator<Item=(&'a T, &'a U)> {
// Zip implementation
a.iter().zip(b)
}
helper(a, b)
}
If not inlined, LLVM should turn zip_array2 into a tiny function.
Perhaps we could invent some way to tag as compile-time only some of the constants. So with code like this the compiler enforces the two arrays to be of equal lenght, but instantiates only one zip_array3 for all possible N:
fn zip_array3<Phantom<const N: usize>, 'a, T, U>
(a: &'a [T; Phantom<N>], b: &'a [U; Phantom<N>])
-> impl Iterator<Item=(&'a T, &'a U)> {
// Zip implementation
a.iter().zip(b)
}
This requires compiler support because [T; Phantom<N>]
currently is invalid syntax. This idea could be generalized to other situations where const generics are used, to keep compile-time correctness and avoid code bloat.