static-assertions icon indicating copy to clipboard operation
static-assertions copied to clipboard

const_assert is not compatible with const generics

Open c6c7 opened this issue 4 years ago • 7 comments
trafficstars

The following code raises a compiler error. I was expecting const_assert! to statically assert the const generic fits the bound.

Code Snippet

use static_assertions;

const SHA256_DIGEST_LENGTH: usize = 32;

fn truncate_sha256_digest<const L: usize>(
    digest: &[u8; SHA256_DIGEST_LENGTH as usize],
) -> [u8; L] {
    const_assert!(L <= SHA256_DIGEST_LENGTH);
    let mut res = [0u8; L];
    res.copy_from_slice(&digest[..L]);
    res
}

Error

error[E0401]: can't use generic parameters from outer function
  --> ...
   |
32 |     fn truncate_sha256_digest<const L: usize>(
   |                                     - const parameter from outer function
...
35 |         const_assert!(L <= SHA256_DIGEST_LENGTH);
   |                       ^ use of generic parameter from outer function

c6c7 avatar Mar 25 '21 21:03 c6c7

Hi, has any one found a way around this issue?

korken89 avatar Apr 04 '21 10:04 korken89

It seems to be possible in a limited form, using a combination of const generics and associated constants. Playground link.

EDIT: I made a crude macro.

0e4ef622 avatar May 21 '21 20:05 0e4ef622

Nice!

korken89 avatar May 23 '21 12:05 korken89

Any update on this issue? The primary reason I found and tried static_assertions is precisely to verify relationships between const generic parameters.

The work-around seems like a completely different approach, correct? Is there a crate for the work-around?

nathan-at-least avatar Apr 15 '22 20:04 nathan-at-least

EDIT: I made a crude macro.

I think there is a tiny error in it. It should be:

     ($($list:ident : $ty:ty),* => $expr:expr) => {{
-        struct Assert<$(const $list: usize,)*>;
+        struct Assert<$(const $list: $ty,)*>;
         impl<$(const $list: $ty,)*> Assert<$($list,)*> {
             const OK: u8 = 0 - !($expr) as u8;
         }
         Assert::<$($list,)*>::OK
     }};

Moreover, it's (meanwhile?) possible to use assert! inside the macro:

         impl<$(const $list: $ty,)*> Assert<$($list,)*> {
-            const OK: u8 = 0 - !($expr) as u8;
+            const OK: () = assert!($expr);
         }
         Assert::<$($list,)*>::OK
     }};
     ($expr:expr) => {
-        const OK: u8 = 0 - !($expr) as u8;
+        const OK: () = assert!($expr);
     };

And I think the scope of the second OK constant should be limited:

-    ($expr:expr) => {
+    ($expr:expr) => {{
         const OK: () = assert!($expr);
-    };
+    }};

(Playground)

JanBeh avatar Feb 20 '23 03:02 JanBeh

Moreover, it's (meanwhile?) possible to use assert! inside the macro:

I don't think so. https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=bee60f1ca0ac361c93cb5ac937e891c0

Honestly I don't even think this is a static-assertions problem, I believe this is a Rust problem.

rydamerell avatar Mar 07 '23 07:03 rydamerell

Okay, I found a workaround:

The assert thing does work, you just have to reference the const at some point in the control flow of the program.

pub struct Test<const X: usize> {}

impl<const X: usize> Test<X> {
    const CHECK: () = assert!(X < 10);

    fn new() -> Self {
        let _ = Self::CHECK; // this is necessary for CHECK to evaluate at comp time
        Self {}
    }
}

pub fn main() {
    let test = Test::<20>::new();
}

Neither CHECK nor _ change the compiled binary at all, but both must be present to get compile-time checking.

rydamerell avatar Mar 07 '23 21:03 rydamerell