Unreachable variants of enum still impact layout

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:

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> {
    // 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)),


1 Like

You probably want to post this to IRLO, where the language design discussions generally take place. This forum is more for helping people understand how to write Rust code.

1 Like

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.