Did you know it's possible to determine if a type is impossible to construct?
Edit: I discovered all too late that this method doesn't work in all cases. I could have sworn that I tested the failing case already, but I guess not. Sorry about that, folks. I'll come back and post an update if I manage to get it working for all cases.
I've been working on a low-level library that includes a bunch of compile time assertion functions that can do a wide variety of things.
One of the functionalities of the checks module is a check that can determine whether or not a type has a niche, which is to say, you can determine if Option<T> has the same size as T. Simple enough. But I wanted to also have an assertion that could assert minimum number of niches. While working on the solution to that problem, I stumbled across the solution to a problem that I wasn't even looking for the solution for. I discovered a way to check if a type is impossible to construct in safe Rust.
Here is an example of a type that is impossible to construct:
pub enum ImpossibleZst {}
This enum has no variants, which means that there's no way to create an instance of it. That also makes it a zero-sized type, and there's no way to apply a repr to these enums that will change the size, so they will always be zero-sized.
I should probably take some time to explain niches in Rust to those that might not have heard of them. In Rust, niches are bit representations that are considered impossible to construct for a type. For example, the NonNull type as well as the NonZero types have a single niche, which makes it possible for Option<NonNull<T>> to have the same size and alignment as NonNull<T>.
This is a very useful memory saving optimization that the Rust foundation has provided us with.
Now that I've gotten that out of the way, I'll explain the trick that makes it possible to determine if a type is impossible to construct.
First, you'll need some helper types.
A type that occupies 255 niches, and has one typed variant. We'll call this Niche255.
pub enum Niche255<T> {
T(T), Nx01, Nx02, Nx03, ..., Nxff,
}
I'll spare you the wall of variants. But trust me, this enum has 256 variants total.
You can't apply repr(C, packed) to this enum, but we'll need a packed type or else this won't work.
#[repr(C, packed)]
pub struct PackedNiche255<T>(Niche255<T>);
Finally, we'll need another packed type, but this one will have a "header" byte.
#[repr(C, packed)]
pub struct PackedWithByte<T>(u8, T);
Now that we have these helper types, we can now check whether or not a type is impossible to construct:
pub const fn is_impossible_to_construct<T>() -> bool {
size_of::<PackedNiche255<Option<T>>>() == size_of::<PackedWithByte<T>>()
}
And that's all there is to it.
Now for the explanation.
Option<T> needs two discriminants, one of the Some variant, and one for the None variant. For some types, Option<T> (and other similar enums) can store the discriminant in of the None variant as one of the impossible bit representations of the T type. As I said before, this makes it possible for Option<T> to have the same size as T when T has impossible representations.
With Niche255, 255 discriminant slots are occupied already. The only slot remaining is for the T variant. If T is a zst (zero-sized type), then Niche255<T> will a size of 1.
When you then store an Option<T> inside Niche255, it needs at least one discriminant for the None variant. If the Some variant is impossible to construct, then it does not need the discriminant for Some, which means that there will be 256 total occupied slots for discriminants. If the type of T were possible to construct, then Option<T> would need two discriminants, and as a result Niche255<Option<T>> would occupy 257 discriminant slots, meaning that the discriminant will occupy two bytes. If the type were impossible to construct, the discriminant would only be a single byte, since there would only be 256 slots occupied. This means that the size of PackedNiche255<Option<T>> is 1 byte greater than T if T is impossible to construct, but if it is possible to construct, it will be 2 bytes greater.
I hope you enjoyed my write-up, I haven't done one of these in a while.
Edit: Playground Link
Also, I have been informed that the correct terminology is inhabited and uninhabited. Sorry for the confusion to anyone that understood the difference unlike myself.