How to loop in const generics

I want to something like this:

struct Solver<const N: usize> { ... }
fn main() {
    for i in (0..n) {
         let t  = Solver::<i>::new();
         println!("{:?}", t);
    }
}

How to do like this? Is there any macro that lets me do like this?

There are a couple ways I could interpret your question: one being that you're willing to unroll the loop by using a macro, and the other being that you literally want to run code in a loop with n determined at runtime.

As to unrolling the loop with a constant n, there's the seq-macro crate:

fn main() {
    seq_macro::seq!(N in 0..=10 {
         let t = Solver::<N>::new();
         println!("{:?}", t);
    });
}

But for a dynamic n:

One of the strange things to keep in mind about const generics is that each Solver<i> is a different type with potentially a different size.. This is one reason I would avoid overusing const generics unless they're absolutely necessary -- I believe their primary motivation was to support strongly typed arrays. I tend to think of it like this: for each type in your code, the compiler needs to know what size that type is and allocate space for it. Thus, since each Solver<i> is potentially a different size, you'll need a different code path for each potential size you're working with. Dynamically sized heap-allocated types like Vec get around this by manually allocating memory on the heap using unsafe.

In essence your pseudo-code is "for each iteration of the loop, store a different type in the variable t". The only way I can think of to accomplish this, for a reasonably small n, is by using an enum:

#[derive(Debug)]
enum SolverEnum /* sorry, for lack of a better name */ {
    Solver0(Solver<0>),
    Solver1(Solver<1>),
    Solver2(Solver<2>),
    Solver3(Solver<3>),
    // ...
}

impl SolverEnum {
    fn new(i: usize) -> Self {
        match i {
            0 => SolverEnum::Solver0(Solver::new()),
            1 => SolverEnum::Solver1(Solver::new()),
            2 => SolverEnum::Solver2(Solver::new()),
            3 => SolverEnum::Solver3(Solver::new()),
            _ => unimplemented!("SolverEnum of size {}", i)
        }
    }
}

fn main() {
    for i in (0..3) {
         let t  = SolverEnum::new(i);
         println!("{:?}", t);
    }
}

That seems fairly cumbersome, and your SolverEnum ends up being as big is the biggest variant you've defined. You could eliminate the latter disadvantage by defining an object-safe trait and returning that instead:

trait SolverTrait: std::fmt::Debug {}

impl<const N: usize> SolverTrait for Solver<N> {}

fn new_solver(i: usize) -> Box<dyn SolverTrait> {
    match i {
        0 => Box::new(Solver::<0>::new()),
        1 => Box::new(Solver::<1>::new()),
        2 => Box::new(Solver::<2>::new()),
        3 => Box::new(Solver::<3>::new()),
        _ => unimplemented!("Solver of size {}", i)
    }
}

It seems it's probably easier to just define a second DynSolver type which uses a Vec rather than an array internally, if you can use the heap.

2 Likes

I actually didn't really want to use const generics. The reason was needed because, there is a method in Solver like this
fn solve(&self, prob: Problem<N>) -> Answer
Here N is usize which should be same as Solver. This rule can be upholded at compile time. But, at the cost of lack of dynamic resolution. Otherwise I have to maually check it and return Option<Answer>.
For my case, seq-macro is actually what I need.
Thanks for the answer

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.