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>() {...}