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?
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.
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.