Fixed-size lazy iterators?

I did some more thinking and came up with the following:

// Machinery for type level if->0-then-else
// It is a bit unfortunate that we need all this boilerplate.
struct Nat<const N: usize>(PhantomData<[(); N]>);
trait IfSuccFn<S, Z> { type Out; }
// If Succ(N) => S
impl<const N: usize, S, Z> IfSuccFn<S, Z> for Nat<{N + 1}> { type Out = S; }
// If 0 => Z
impl<S, Z> IfSuccFn<S, Z> for Nat<{0}> { type Out = Z; }
// if N > 0 then S, else Z.
type IfSucc<const N: usize, S, Z> = <Nat<N> as IfSucc<S, Z>>::Out;

trait CSIterator<const N: usize> {
    type Item;
    type Next: CSIterator<{ N.saturating_sub(1) }, Item = Self::Item>;
    fn next(self) -> (IfSucc<N, Self::Item, ()>, Self::Next);
}

fn foreach<Iter: CSIterator<N>, F: FnMut(Iter::Item), const N>(iter: Iter, fun: F) {
    if N > 0 {
        let (item, next) = iter.next();
        fun(item);
        foreach(next);
    }
}

The main benefit of this encoding (assuming it actually type checks) is that we are not involving Option at all.

EDIT: Improving readability of the encoding a bit:

// Machinery for type level if-then-else
struct Test<const B: bool>(PhantomData<[(); B as usize]>);
trait IfFn<S, Z> { type Out; }
impl<S, Z> IfFn<S, Z> for Test<{true }> { type Out = S; }
impl<S, Z> IfFn<S, Z> for Test<{false}> { type Out = Z; }

// if B then S, else Z.
type If<const B: bool, S, Z> = <Test<B> as IfFn<S, Z>>::Out;

trait CSIterator<const N: usize> {
    type Item;
    type Next: CSIterator<{ N.saturating_sub(1) }, Item = Self::Item>;
    fn next(self) -> (If<{N == 0}, (), Self::Item>, Self::Next);
}

Ideally, you would really want to write:

trait CSIterator<const N: usize> {
    type Item;
    type Next: CSIterator<{ N.saturating_sub(1) }, Item = Self::Item>;
    fn next(self) -> (if N == 0 { () } else { Self::Item }, Self::Next);
}

and that’s it.