Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Unconstrained generic constant" when adding const generics

Tags:

rust

How would I add const generics? Lets say I have a type foo:

pub struct foo <const bar: i64> {
    value: f64,
}

and I want to implement mul so I can multiply 2 foos together. I want to treat bar as a dimension, so foo<baz>{value: x} * foo<quux>{value: k} == foo<baz + quux>{value: x * k}, as follows:

impl<const baz: i64, const quux: i64> Mul<foo<quux>> for foo<baz> {
    type Output = foo<{baz + quux}>;

    fn mul(self, rhs: foo<quux>) -> Self::Output {
        Self::Output {
            value: self.value * rhs.value,
        }
    }
}

I get an error telling me I need to add a where bound on {baz+quux} within the definition of the output type. What exactly does this mean and how do I implement it? I can't find any seemingly relevant information on where.

like image 566
Dyspro Avatar asked Dec 30 '22 16:12

Dyspro


1 Answers

The solution

I got a variation on your code to work here:

impl<const baz: i64, const quux: i64> Mul<Foo<quux>> for Foo<baz>
    where Foo<{baz + quux}>: Sized {
    type Output = Foo<{baz + quux}>;

    fn mul(self, rhs: Foo<quux>) -> Self::Output {
        Self::Output {
            value: self.value * rhs.value,
        }
    }
}

How I got there

I've reproduced the full error that you get without the added where clause below:

error: unconstrained generic constant
  --> src/main.rs:11:5
   |
11 |     type Output = Foo<{baz + quux}>;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
help: try adding a `where` bound using this expression: `where [u8; {baz + quux}]: Sized`

Now, the clause that it suggests is not very useful, for one reason: the length parameter of a statically sized slice must be a usize, but our values baz and quux (and their sum) are i64. I'd imagine that the compiler authors included that particular suggestion because the primary use case for const generics is embedding array sizes in types. I've opened an issue on GitHub about this diagnostic.

Why is this necessary?

A where clause specifies constraints on some generic code element---a function, type, trait, or in this case, implementation---based on the traits and lifetimes that one or more generic parameters, or a derivative thereof, must satisfy. There are equivalent shorthands for many cases, but the overall requirement is that the constraints are fully specified.

In our case, it may seem superficially that this implementation works for any combination of baz and quux, but this is not the case, due to integer overflow; if we supply sufficiently large values of the same sign for both, their sum cannot be represented by i64. This means that i64 is not closed under addition.

The constraint that we add requires that the sum of the two values is in the set of possible values of an i64, indirectly, by requiring something of the type which consumes it. Hence, supplying 2^31 for both baz and quux is not valid, since the resulting type Foo<{baz + quux}> does not exist, so it cannot possibly implement the Sized trait. While this technically is a stricter constraint than we need (Sized is a stronger requirement than a type simply existing), all Foo<bar> which exist implement Sized, so in our case it is the same. On the other hand, without the constraint, no where clause, explicit or shorthand, specifies this constraint.

like image 195
Nick Mertin Avatar answered Feb 15 '23 23:02

Nick Mertin