I've noticed some behavior in Rust's struct and enum layouts that I think could be improved (w.r.t. zero sized and unconstructable types).
I am currently using static type arguments to enable/disable variants of a type used to provide matching (instances of the matching behavior not included). I had hoped that we'd only pay (in size) for the enabled variants, but it seems that the enums are the size of the largest variant, not just the largest construct-able variant, leading to much larger sizes than hoped.
Please consider the following playground code which demonstrates this:
#[allow(unused_imports)]
use core::marker::PhantomData;
use std::fmt::Debug;
use std::mem::size_of;
// assert_eq! fails fast so we can't see the full picture. Use this instead.
macro_rules! check {
($left: expr, $right: expr) => {
let left = $left;
let right = $right;
if left == right {
eprintln!(" PASS: {} == {}", stringify!($left), stringify!($right));
} else {
if format!("{:?}", right) == stringify!($right) {
eprintln!(" FAIL: {} => {:?} \texpected {}", stringify!($left), left, stringify!($right));
} else {
eprintln!(" FAIL: {} => {:?} \texpected {} => {:?}", stringify!($left), left, stringify!($right), right);
}
}
};
}
// Marker trait just used to avoid accidents.
trait IsAllowed {}
// Implements a Void/Never/False type
// (note instances are derived for ease of use but Never has no values).
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
enum Never {}
impl IsAllowed for Never {}
// Implements an Always/True type.
// (note instances are trivial and size is 0).
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
struct Always;
impl IsAllowed for Always {}
// Here we make some enum variants impossible to construct via type arguments.
// This essentially disables some variants at compile time.
enum Matchable<'a, AnyAllowed: IsAllowed=Never, FuncAllowed: IsAllowed=Never, T=i32> {
Id(T),
Any(AnyAllowed),
#[allow(dead_code)]
// Func(FuncAllowed, PhantomData<Box<dyn 'a + Debug>>), // THIS EXHIBITS CORRECT SIZING.
Func(FuncAllowed, &'a dyn Fn(&T) -> bool), // THIS DOES NOT.
// The two should be the same size iff FuncAllowed is Never.
}
use Matchable::*;
fn main() {
eprintln!("Ensure the values are constructable:");
check!(format!("{:?}", Id(3) as Matchable), "Id(3)");
check!(format!("{:?}", ANY), "ANY");
eprintln!("Sizes for reference:");
check!(size_of::<()>(), 0);
check!(size_of::<i32>(), 4);
check!(size_of::<()>(), 0);
eprintln!("Fail due to impossible enum variants still impacting layout:");
check!(size_of::<Matchable<Never, Never, ()>>(), 0);
check!(size_of::<Matchable<Always, Never, ()>>(), 1);
check!(size_of::<Matchable<Never, Never, i32>>(), 4);
check!(size_of::<Matchable<Always, Never, i32>>(), 8);
eprintln!("Fail when using the PhantomData solution as the function ptr is not carried:");
check!(size_of::<Matchable<Never, Always, ()>>(), 16);
check!(size_of::<Matchable<Always, Always, ()>>(), 24);
check!(size_of::<Matchable<Never, Always, i32>>(), 24);
check!(size_of::<Matchable<Always, Always, i32>>(), 24);
}
// Helpers:
const ANY: Matchable<'static, Always, Never> = Any(Always);
impl <'a, T: Debug, AnyAllowed: IsAllowed, FuncAllowed: IsAllowed> Debug for Matchable<'a, AnyAllowed, FuncAllowed, T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match self {
Id(v) => write!(f, "Id({:?})", v),
Any(_allowed) => write!(f, "ANY"),
Func(_allowed, func) => write!(f, "<func {:?}>", std::ptr::addr_of!(func)),
}
}
}
Reposting from: Unreachable variants of enum still impact layout - help - The Rust Programming Language Forum As I did want help but it was suggested this was more about language design. Sorry for the noise.