How to write constraint on associated const?

For example, I have the following trait

trait Foo {
    const FOO: usize;
}

And how to write constraint to make sure FOO is less than 4? The following code does not compile:

fn foo<T: Foo>() where <T as Foo>::FOO < 4 {}

@camden-smallwood That doesn't make any sense. Both [u8; 1] and [u8; 0] are Sized, so I don't see how that can work.

Ah my apologies, I see how that works both ways. Removed.

As a (very ugly !) alternative on stable, you might be able to use some type magic. This is far from idiomatic though :sweat_smile:

I looked into it a bit further and came up with a revised method which was brought up by @withoutboats here:

This still requires nightly of course, is not exactly ideal either, but it does work for this use case:

#![allow(incomplete_features)]
#![feature(const_generics, const_evaluatable_checked)]

struct If<const B: bool>;

trait True {}

impl True for If<true> {}

trait Foo {
    const FOO: usize;
}

fn foo<T: Foo>() where If<{<T as Foo>::FOO < 4}>: True {}

Const generic has similar functionalities with associated const to some extent, using const generic does solve this problem. And in stable rust, it seems that only type magic can achieve this goal?

I wonder if there are RFCs which specifies constraint of associated const, since I think "associated const" is in an awkward position, where it has few things to do (even can't have an constraint), and most of its functionalities can be replaced by const generic.

Well, it is related to const generics just in the same way the associated types are related to ordinary generics: associated items describe things which are decided by the implementation, but generic parameters (types or constants) describe things which are decided by user.

Maybe this approach works for you

trait Foo {
    const FOO: usize;
}

trait CheckFooLessThanFour: Foo {
    const CHECK: ();
}
impl<T: Foo + ?Sized> CheckFooLessThanFour for T {
    const CHECK: () = [()][(Self::FOO >= 4) as usize];
}

fn foo<T: Foo + CheckFooLessThanFour>() {
    // make sure to “use” `CHECK` in the function body!
    let _ = T::CHECK;
}

struct PassesCheck;
struct WontPassCheck;
impl Foo for PassesCheck {
    const FOO: usize = 3;
}
impl Foo for WontPassCheck {
    const FOO: usize = 4;
}

fn main() {
    foo::<PassesCheck>(); // compiles
    // foo::<WontPassCheck>(); // doesn’t compile
}
1 Like

Thanks, this works for me. Just for curiosity, could you explain to me what does the following code do?

const CHECK: () = [()][(Self::FOO >= 4) as usize];

It seems that [(Self::Foo >= 4) as size] is take subscript?

If it is less than four, then (Self::FOO >= 4) as usize is zero, and if larger/equal to four, it is one, but indexing into the array [()] fails if it is one.

Yes.. since assert! isn’t usable in constants (yet), this workaround (indexing a 1-element array with a bool converted to either 0 or 1) is a way to conditionally create an error in a const calculation.