How do I static assert a property of a generic u32 parameter?

I tried a bunch of different ways based on reading snippets, and I haven't found any that compile. Interestingly rustc doesn't even suggest any feature flags for this which makes me wonder if what I'm trying to do is impossible even with nightly?

//fn test<const N: u32>()
//{
//    const _ = assert!(N % 4 == 0);
//}

//fn test2<const N: u32>()
//    where [(); N % 4 == 0]:
//{
//}

//const fn check(n: u32) -> bool
//{
//    return n % 4 == 0;
//}

//fn test3<const N: u32>()
//{
//    const _ = assert!(check(N));
//}

//const fn check2<N: u32>() -> bool
//{
//    return N % 4 == 0;
//}

//fn test4<const N: u32>()
//{
//    const _ = assert!(check::<N>());
//}

Playground link. In the past I did get this kind of check working once with typenum, but the compiler errors were worse than the ones I'd get in C++ so I don't think that's a practical route esp. if I have to support this code being used by others.

One workaround (which I found here) is to evaluate an associated constant (Rust Playground):

struct AssertDivisible<const N: u32, const D: u32>;

impl<const N: u32, const D: u32> AssertDivisible<N, D> {
    const OK: () = assert!(N % D == 0, "must be divisible");
}

fn test<const N: u32>() {
    let () = AssertDivisible::<N, 4>::OK;
}

This can be adapted to pretty much any constant-evaluated condition. However, a new type must be used for every kind of condition, since no operations can be done on the const generic parameters themselves.

2 Likes

Not sure I understand. It looks like you are doing operations on generic parameters? I think you mean you need a new type each time because if you put multiple associated consts for different conditions in the same impl they would all get triggered at once when the impl is instantiated?

#![feature(generic_const_exprs)]
#![allow(incomplete_features)]

fn test2<const N: u32>()
    where [(); (N % 4 == 0) as usize -1]: {
}
3 Likes

What I mean is that the actual arithmetic and comparisons must be done in the value of the associated constant. The type parameters to Assert must pass through the values directly. So we can't write, e.g., struct Assert<const COND: bool>; with const OK: () = assert!(COND); and test Assert::<{ N % 4 == 0 }>::OK, at least not without generic_const_exprs. As a collorary, if we want to test several different conditions, we need several different Assert types, each with a condition in its OK constant.

2 Likes

Got it, thanks👍

What about using const_assert!() from the static-assertions crate?

const VALUE: i32 = // ...

static_assertions::const_assert!(VALUE >= 2);

With nightly you have @vague's answer, which is the proper solution.


On stable, you can use post-monomorphization errors (panic!s when resolving generic constants) to emulate that, but it's really a poorman's substitute, since cargo check won't detect those failures, only the final cargo build can, and it will only do so if such a function is deemed reachable enough for it to make it to codegen (thence triggering a monomorphization of that generic const, and thus, observing the panic). This is what @LegionMammal978's solution is about, but it was missing these obligatory disclaimers about the limitations of post-mono errors

  • In other words: if you're compiling the final binary, an approach based on post-mono checks is fine. But if you're just a library, then you may make mistakes which you'll never detect and only a downstream user will, which will be highly inconvenient for them. So I would advise against post-mono checks for library authors.

On the other hand, @LegionMammal978's approach has the advantage of using a compile-time panic for a way nicer error message. So we can take that idea and apply it to the nightly approach, yielding:

struct AssertDivisibleBy4<const N: u32>;

impl<const N: u32> AssertDivisibleBy4<N> {
    const OK: usize = {
        assert!(N % 4 == 0, "must be divisible by 4");
        0
    };
}

fn test2<const N: u32> ()
where
    [(); AssertDivisibleBy4::<N>::OK]:,
{
    // …
}

so that a test2::<3>, even in unreachable code, yields:

error[E0080]: evaluation of `AssertDivisibleBy4::<3_u32>::OK` failed
 --> src/lib.rs:8:9
  |
8 |         assert!(N % 4 == 0, "must be divisible by 4");
  |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'must be divisible by 4', src/lib.rs:8:9
  |
2 Likes