Constant evaluation in where clauses

Forgive me if this topic has been brought up before. I can’t find anything via search.

With the introduction of constant generics and more broadly the notion of constant expressions and evaluation, it follows that there should be some method to apply constraints to const generics in a manner similar to how constraints are placed on type parameters.

For a motivating example, it would be nice to define structs such as this in as generic of a way possible:

struct Vector<T, const ARITY: usize>
where
    {ARITY > 0};

Another way this could be done is through a bunch of boiler plate in a manner similar to:

trait True {}

struct Bool<const TERM: bool>();

impl True for Bool<{true}> {}

struct Vector<T, const N: usize>([T; N])
where
    Bool<{N > 0}>: True;

This does not currently work, and in fact the compiler error generated by this (the trait bound Bool<{N > 0}>: True is not satisfied) conforms to the text of the const generic PRD.

1 Like

Currently only the bare bones of const-generics are implemented. Also, (currently) all expressions are treated as a black box, so {x+1} is not the same as {x+1} in two different locations. This might throw a wrench in your plans until const-generics becomes more powerful.

yes, it would be quite some time before anything like this could be implemented at all. I’m not posting an RFC or anything for that reason.

The purpose of this is to spur discussion into whether or not the feature is desirable, and if it is desirable, to spur discussion into what the syntax and semantics for such a feature would be

One could also imagine doing this by bounding the impls instead of the type (as the standard library usually does) perhaps like

impl<T, const N: usize> Vector<T, {1 + N}> { ... }

But again, that hits the questions @Yato already mentioned.

This actually won’t work, similar to how

impl<T: Add> Trait for T::Output { ... }

Won’t work, expressions are treates the same way as associate types. Rust will complain that N is unconstrained.

2 Likes

Indeed, I think for this sort of feature to be possible at all, it will have to be through a where clause.

My reasoning for this is that a guard is not generally considered part of the type signature of an item beyond default specialization.

Consider the following code which we would like to avoid supporting:

impl<T, const N: usize> Vector<T, {N}>
where
       { N > 4 },
{ 
       fn new() -> Self { /* implementation for greater than four! */ }
}
 impl<T, const N: usize> Vector<T, {N}>
where
       { N <= 4 },
{ 
       fn new() -> Self { /* implementation for not that! */ }
}

This code failing to typecheck is intuitive as it would fail to typecheck if the guards were type constraints.

In this specific case you could just do

impl<T, const N: usize> Vector<T, {N}>
{ 
    fn new() -> Self {
        if N > 4 {
            /* implementation for greater than four! */
        } else {
            /* implementation for not that! */
        }
    }
}

And const propagation will eliminate the other branch. But in general, we wouldn’t want to do this.

1 Like

Indeed, nothing is lost in terms of optimization ability. Constant evaluation in where clauses is basically intended to be a static assertion to avoid certain potentially buggy or otherwise useless or illogical implementations.

1 Like

Yes, I agree that this would be very useful. Especially for cases like

impl<T, const N: usize> Vector<T, {N}>
{ 
    fn x(&self) -> &T where { N > 0 } {
       // ... snip ...
    }
}

Where it only makes sense to get the x component of a vector when there is an x component, and get a compile time error if the vector is 0 sized

2 Likes

Just to comment on syntax,empty where clauses right before method bodies are valid:

#![feature(const_generics)]

struct Foo<const N:usize>;

impl<const N:usize> Foo<{N}>{
    fn x() -> bool where { N > 0 }
}

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=40889e794e6e072d4e477f2d50c04e29

2 Likes

Since it hasn’t been linked yet, the accepted const generics RFC talked about the need for future work to figure out value unification: https://rust-lang.github.io/rfcs/2000-const-generics.html#equality-of-two-abstract-const-expressions

Some work around this happened as part of the pi types trilogy:

3 Likes