Michał Zabielski
Michał Zabielski

Reputation: 434

Const Generics - how to ensure that usize const is > 0

Given sample struct:

struct S<const N: usize> {
    arr: [u32; N] // I want size to be N > 0
}

I want to have N > 0.

In other words, I want to limit available values of N to be from range 1...MAX_USIZE, and not 0...MAX_USIZE.

Upvotes: 7

Views: 3516

Answers (3)

Lucraft
Lucraft

Reputation: 73

If you want to use a where clause you can do it like this:

#![feature(generic_const_exprs)]

pub enum Assert<const CHECK: bool> {}
pub trait IsTrue {}
impl IsTrue for Assert<true> {}

struct S<const N: usize> {
    arr: [u32; N]
}

impl<const N: usize> S<N>
where
    Assert<{N > 0}>: IsTrue
{
    const fn new(arr: [u32; N]) -> Self {
        S { arr }
    }
}

Note:

  • this uses an unstable feature (generic_const_exprs) and you need to use the nightly compiler for this
  • you can not define a custom error message (mismatched types⏎expected constant false⏎ found constant true)

More about const generics in rust can be found on practice.rs (The Assert example can be found at the bottom of the page)

Upvotes: 5

Chayim Friedman
Chayim Friedman

Reputation: 71410

I would recommend assert!()ing in the constructor, like @sebpuetz said, however it is possible to do that. This is bad (see this answer of mine for an explanation why, and why this is a hard problem in general), but you can use associated items for that:

struct S<const N: usize> {
    arr: [u32; N],
}

impl<const N: usize> S<N> {
    fn new(arr: [u32; N]) -> Self {
        _ = <Self as AssertGt0>::VALID;
        
        Self { arr }
    }
}

trait AssertGt0 {
    const VALID: ();
}

impl<const N: usize> AssertGt0 for S<N> {
    const VALID: () = assert!(N > 0);
}

fn main() {
    // _ = S::new([]); // This fails compilation
}

Playground.

You cannot do that on where clauses as far as I know, you have to use a constructor.

Upvotes: 5

sebpuetz
sebpuetz

Reputation: 2618

To my knowledge it's not possible to add such constraints to const generics as of now, a workaround is a constructor that asserts N > 0:

struct S<const N: usize> {
    arr: [u32; N] // I want size to be N > 0
}

impl<const N: usize> S<N> {
    const fn new(arr: [u32; N]) -> Self {
        assert!(N > 0, "n must be greater than 0");
        S {
            arr
        }
    }
}

const FAILS_TO_COMPILE: S<0> = S::new([]);

Playground

results in:

error[E0080]: evaluation of constant value failed
  --> src/lib.rs:7:9
   |
7  |         assert!(N > 0, "n must be greater than 0");
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |         |
   |         the evaluated program panicked at 'n must be greater than 0', src/lib.rs:7:9
   |         inside `S::<0_usize>::new` at /rustc/fe5b13d681f25ee6474be29d748c65adcd91f69e/library/core/src/panic.rs:57:9
...
14 | const FAILS_TO_COMPILE: S<0> = S::new([]);
   |                                ---------- inside `FAILS_TO_COMPILE` at src/lib.rs:14:32
   |
   = note: this error originates in the macro `$crate::panic::panic_2021` (in Nightly builds, run with -Z macro-backtrace for more info)

In non-const contexts, this is a runtime error.

Upvotes: 4

Related Questions