Abstracting nested loops

Hi all

I am trying to generalize nested loops construction via generics with const parameters.

#![feature(generic_const_exprs)]

#[inline(always)]
fn iter_dim<F, const N: usize, const XI: usize>(x: &[i64; N], v: &mut [i64; N], f: &mut F)
where
    F: FnMut(&[i64; N]),
    [(); XI + 1]:,
{
    if XI >= N {
        return f(v);
    }

    for i in x[XI] - 1..x[XI] + 2 {
        v[XI] = i;
        iter_dim::<_, N, { XI + 1 }>(x, v, f)
    }
}

fn main() {
    let x: [i64; 3] = [1, 2, 3];
    let mut i: [i64; 3] = [0, 0, 0];
    iter_dim::<_, 3, 0>(&x, &mut i, &mut |i| println!("{:?}", i));
}

However, I receive only following errors using latest nightly compiler:

error: unconstrained generic constant
   --> src/main.rs:217:44
    |
217 |         iter_dim::<_, N, { XI + 1 }>(x, v, f)
    |         ----------------------------       ^
    |         |
    |         required by a bound introduced by this call
    |
    = help: try adding a `where` bound using this expression: `where [(); XI + 1]:`
note: required by a bound in `iter_dim`
   --> src/main.rs:209:10
    |
206 | fn iter_dim<F, const N: usize, const XI: usize>(x: &[i64; N], v: &mut [i64; N], f: &mut F)
    |    -------- required by a bound in this function
...
209 |     [(); XI + 1]:,
    |          ^^^^^^ required by this bound in `iter_dim`

Any help?

although I don't quite understand what your are trying to achieve, from the code, I don't think it's supported. your example would do a recursive call, but the well-formedness of the [(); XI+1] cannot be proved in the recursion step from XI to {XI + 1}.

anyway, const generics in rust currently are not the easiest to work with, after all, the generic_const_exprs feature by default would trigger a incomplete_features warning.

1 Like

Looks like I managed to make it work by myself.

#![feature(generic_const_exprs)]
#![feature(generic_const_items)]
#![feature(generic_arg_infer)]
#![feature(specialization)]

enum Assert<const COND: bool> {}
trait IsTrue {}
impl IsTrue for Assert<true> {}

trait IterDim<const N: usize, const XI: usize, F: FnMut(&[i64; N])> {
    fn iter_dim(x: &[i64; N], v: &mut [i64; N], f: &mut F);
}

struct IxIter<const XI: usize>();

impl<const N: usize, const XI: usize, F: FnMut(&[i64; N])> IterDim<N, XI, F> for IxIter<XI>
where
    Assert<{ XI < N }>: IsTrue,
    [(); XI + 1]:
{
    #[inline(always)]
    fn iter_dim(x: &[i64; N], v: &mut [i64; N], f: &mut F) {
        for i in x[XI] - 1..x[XI] + 2 {
            v[XI] = i;
            IxIter::<{ XI + 1 }>::iter_dim(x, v, f)
        }
    }
}

impl<const N: usize, const XI: usize, F: FnMut(&[i64; N])> IterDim<N, XI, F> for IxIter<XI> {
    #[inline(always)]
    default fn iter_dim(_x: &[i64; N], v: &mut [i64; N], f: &mut F) {
        f(v);
    }
}

#[inline(never)]
fn dump(i:&[i64; 3]) {
    println!("{:?}", i)
}

fn main() {
    let x: [i64; 3] = [1, 2, 3];
    let mut i: [i64; 3] = [0, 0, 0];
    IxIter::<0>::iter_dim(&x, &mut i, &mut |i| dump(i));
}

Prints following as expected:

[0, 1, 2]
[0, 1, 3]
[0, 1, 4]
[0, 2, 2]
[0, 2, 3]
[0, 2, 4]
[0, 3, 2]
[0, 3, 3]
[0, 3, 4]
[1, 1, 2]
[1, 1, 3]
[1, 1, 4]
[1, 2, 2]
[1, 2, 3]
[1, 2, 4]
[1, 3, 2]
[1, 3, 3]
[1, 3, 4]
[2, 1, 2]
[2, 1, 3]
[2, 1, 4]
[2, 2, 2]
[2, 2, 3]
[2, 2, 4]
[2, 3, 2]
[2, 3, 3]
[2, 3, 4]

Compiling in release gives nice all-inlined loops.