How can a generic type exclude zero-sized types?

I'd like a struct to take a generic type T where std::mem::size_of::<T>() >= 1. I can use a const to simulate this, like so:

use std::mem;

pub struct NotZeroSized<T> {
    data: T
}
impl<T> NotZeroSized<T> {
    #[deny(const_err)]
    const _TEST_ZERO_SIZE_: usize = 0 - (mem::size_of::<T>() == 0) as usize;
    
    pub fn new(data: T) -> Self {
        // The next line must be in a function that is
        // called in order to trigger a const error.
        #[allow(path_statements)] { Self::_TEST_ZERO_SIZE_; }
        
        Self { data }
    }
}

But is there a less hacky way to do it?

In standard, idiomatic Rust, you have two options:

  • Handle the zero sized type case. Typically this can be done in a clever way that avoids putting undue strain on the common case; an if mem::size_of::<T>() == 0 will trivially optimize to one case or the other and not performing the actual check.
  • Forbid the use of ZSTs via an unsafe guard at the point of construction.

You need to be very careful if you're using lints, as --cap-lints exists and is used by cargo for dependencies, so you do not see lints that fire in dependencies.

Yeah, that's what I feared. I was trying to figure out how to make an unsafe interface safe, but it only worked if no ZSTs were used. So I'll probably give up trying.