I'm trying out some code with const generics and I'm running into some issues with type inference. Is there any way to make this work without having to specify let _: Foo<4, 0> = ...;? Playground
struct Foo<const N: usize, const M: usize = 0> {
_x: [usize; N],
y: Option<[usize; M]>,
}
impl<const N: usize, const M: usize> Foo<N, M> {
fn new(x: [usize; N]) -> Self {
Self {
_x: x,
y: None
}
}
fn new_without_y(x: [usize; N]) -> Foo<N, 0> {
Foo {
_x: x,
y: None
}
}
fn with_y(mut self, y: [usize; M]) -> Self {
self.y = Some(y);
self
}
}
fn main() {
// This doesn't compile: `Foo<4, 0>` should be inferred
// https://github.com/rust-lang/rust/issues/98931
// let _ = Foo::new([0; 4]);
// This compiles: `Foo<4, 8>` is inferred
let _ = Foo::new([0; 4]).with_y([0; 8]);
// This doesn't compile:
// Even though the return type is Foo<N, 0>, the compiler says it can't infer M
// Not sure if known issue, it seems different from the one related to default const generics
let _: Foo = Foo::new_without_y([0; 4]);
}
Here's the reason it doesn't work. The fully-qualified version of your let _: Foo = Foo::new_without_y([0; 4]); is:
let _: Foo<4, 0> = <Foo<4, M>>::new_without_y([0; 4]);
where M is any integer at all.
This is because your new_without_y method is defined inside an impl block that implements the method for all Foo<N, M> types, regardless of whether M is zero or not.
The fix is to separate new_without_y into a different impl block, that only applies to when M is zero:
this is expected, although not very useful IMO, behavior. the problem is, default generic arguments are not used during inference. some relative readings:
the first error can be worked around by implementing new() for a specialized type:
// Foo<N> is short for Foo<N, 0>
impl<const N: usize> Foo<N> {
fn new(x: [usize; N]) -> Self {
Self { _x: x, y: None }
}
}
let _ = Foo::new([0; 4]);
but this modification will break the second case, since with_y() returns the same type as Self. to work around this, you can also modify with_y() so that it changes the return type:
impl<const N: usize, const M: usize> Foo<N, M> {
// changes the return type based on `y`
fn with_y<const M2: usize>(mut self, y: [usize; M2]) -> Foo<N, M2> {
Foo {
_x: self._x,
y: Some(y),
}
}
}
let _ = Foo::new([0; 4]).with_y([0; 8]);
for the third case, you can use the same technique as the first one, but there's another issue for the annotation: you cannot use _ as placeholder for const generics, so you either need concrete values for const generics in the ascribed type, or you don't use type ascription and rely on type inference only.