Totality musings with some const generics on top


#1

In the Haskell Prelude there are famous partial functions:

Prelude> let l = []
Prelude> head l
*** Exception: Prelude.head: empty list
Prelude>
Prelude> let a = [10, 20]
Prelude> a !! 3
*** Exception: Prelude.!!: index too large
Prelude> a !! (-2)
*** Exception: Prelude.!!: negative index
Prelude>

Because Haskell allow you to write partial matches (with warnings). In Rust matches must be total, but other things are partial:

let a = [10, 20];
let x = a[3];

Total functions that push panics upstream make their source visible in the code, with a less ergonomic APIs (this is a design choice different from the std lib one):

#![allow(unused_variables)]
fn main() {
    use std::num::NonZeroUsize;
    let n = NonZeroUsize::new(2).unwrap();
    for i in (0 .. 10).step_by(n) {}
    let aslice = b"rust";
    let iter1 = aslice.windows(n);
    let iter2 = aslice.chunks(n);
}

Const generics should allow you to design APIs that regain a sufficient ergonomy (they give a compilation time if the const argument is zero):

#![allow(unused_variables)]
fn main() {
    for i in (0 .. 10).step_by_ct<2>() {}
    let aslice = b"rust";
    let iter1 = aslice.windows_ct<2>();
    let iter2 = aslice.chunks_ct<2>();
}

windows_ct and chunks_ct could yield references to fixed-size arrays, that allows very handy pattern matching:

let v = vec![1, 2, 3];
let arr_ref1 = v.as_slice_ct<3>().unwrap();
let arr_ref2 = v.get_ct<1, 3>().unwrap();
let [a, b] = *arr_ref2;
let aslice = b"rust";
for &[a, b] in aslice.windows_ct<2>() {...}