Is it possible to assert at compile time that foo<T>() is not called with a ZST?

Assuming an API that cannot work properly with ZSTs, I can of course document that and assert/panic at runtime.

However, is there a trick (akin to what static_assertions does - but that doesn't seem to work here) to assert this at compile time?

1 Like

Something like this may work for you

trait PanicWhenZeroSized: Sized {
    const _CONDITION: usize = (std::mem::size_of::<Self>() == 0) as usize;
    const _CHECK: &'static str = ["type must not be zero-sized!"][Self::_CONDITION];
    #[allow(path_statements, clippy::no_effect)]
    fn assert_zero_size() {
        <Self as PanicWhenZeroSized>::_CHECK;
    }
}

impl<T> PanicWhenZeroSized for T {}

fn foo<T>() {
    let _ = T::assert_zero_size();
}

fn main() {
    foo::<u8>();
    // foo::<()>();
}

Uncommenting the foo::<()> line gives

   Compiling playground v0.0.1 (/playground)
error: any use of this value will cause an error
 --> src/main.rs:3:34
  |
3 |     const _CHECK: &'static str = ["type must not be zero-sized!"][Self::_CONDITION];
  |     -----------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
  |                                  |
  |                                  index out of bounds: the length is 1 but the index is 1
  |
  = note: `#[deny(const_err)]` on by default
  = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
  = note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>

error: erroneous constant encountered
 --> src/main.rs:6:9
  |
6 |         <Self as PanicWhenZeroSized>::_CHECK;
  |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: could not compile `playground` due to 2 previous errors
7 Likes

Actually, apparently we can do panic in const fn now and it gives a better error message:

const fn check_non_zero_sized<T>() {
    if std::mem::size_of::<T>() == 0 {
        panic!("type is zero-sized");
    }
}

trait PanicWhenZeroSized: Sized {
    const _CHECK: () = check_non_zero_sized::<Self>();
    #[allow(path_statements, clippy::no_effect)]
    fn assert_zero_size() {
        <Self as PanicWhenZeroSized>::_CHECK;
    }
}

impl<T> PanicWhenZeroSized for T {}

fn foo<T>() {
    let _ = T::assert_zero_size();
}

fn main() {
    foo::<u8>();
    foo::<[u8; 0]>();
}
   Compiling playground v0.0.1 (/playground)
error[E0080]: evaluation of `<[u8; 0] as PanicWhenZeroSized>::_CHECK` failed
 --> src/main.rs:3:9
  |
3 |         panic!("type is zero-sized");
  |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |         |
  |         the evaluated program panicked at 'type is zero-sized', src/main.rs:3:9
  |         inside `check_non_zero_sized::<[u8; 0]>` at /rustc/2885c474823637ae69c5967327327a337aebedb2/library/core/src/panic.rs:57:9
...
8 |     const _CHECK: () = check_non_zero_sized::<Self>();
  |                        ------------------------------ inside `<[u8; 0] as PanicWhenZeroSized>::_CHECK` at src/main.rs:8:24
  |
  = note: this error originates in the macro `$crate::panic::panic_2021` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0080`.

Note that neither version actually errors if the only use-case is in an unused private function.

Edit: Actually, this only works in the upcoming stable 1.57 (almost 2 weeks from now), or current beta or nightly.

4 Likes

With unstable incomplete features, you can also do something like

#![feature(generic_const_exprs)]
fn foo<T>()
where
    [(); std::mem::size_of::<T>() - 1]:,
{
}

fn main() {
    foo::<u8>();
    foo::<()>();
}

or maybe something like

#![feature(generic_const_exprs)]

trait Assertion<const COND: bool> {}

impl Assertion<true> for () {}

fn foo<T>()
where
    (): Assertion<{ std::mem::size_of::<T>() != 0 }>,
{
}

fn main() {
    foo::<u8>();
    foo::<()>();
}
3 Likes

Thanks a lot! Actually for the use case it should work on 1.48, so the first looks like what is needed :slight_smile:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.