Pre-RFC: packed enums

Extremely early pre-RFC, so it's pretty informal.

Currently, enums are always spaced out with their discriminant occupying a full 32-bit integer unless they're #[repr(u8)] or similar. They do already seem to be generally flattened into a single unified discriminant value (though not perfectly), but I'd like to be able to additionally leverage all the struct packing logic to save a lot of space for simpler enums when I only care that it's as small as pragmatically possible.

Likewise, packed enums should ideally just use a single register where possible. If it's a Result<Option<Option<u8>>, u8>, I could imagine the u8 just being shifted up 8 bits in the result to be aligned to a byte boundary, but the whole result still being packed in a single register, potentially giving a speed boost due to less register pressure.

This does relate to Pre-RFC: Allow array stride != size but is mostly orthogonal.

Look up "move-only fields", and related discussions.

The reason that more aggressive packing can't be done already is that you can get a &mut to the fields, which means that it can't overlap anything else, etc.

But if we add a new kind of field that can only be moved (realistically copied usually), not referenced, then the compiler always has the opportunity to run code when reading/writing them, and thus it can split types up, put things in padding, even arithmetic-coding combine things if you wanted.

So my instinct is that we should add move-only fields to the language, and then the compiler can go wild figuring out what optimizations make sense for repr(Rust) without needing to RFC all of those things. (For example, struct Foo { move-only x: (u16, u8), y: u8 } could be 4 bytes if we wanted. Strawman syntax, of course, but move is a keyword so that would be possible.)

The compiler has a bunch of code around restricting borrowing on fields for repr(packed), so hopefully just prohibiting it entirely could take advantage of some of that, and maybe even a future edition could require move-only fields for repr(packed).

2 Likes

I expected as much since it's already unsafe to reference fields in packed structs. Definitely not safe to reference an inner enum when it doesn't truly exist at that location!

Or you could take a simpler route and just ban attempts to reference inner fields of packed enums, requiring copying or moving as applicable instead. Not sure we need a new field modifier for this when the restrictions would exist for all fields anyways.

The restriction doesn't need to exist for everything, though.

Take AdtFlags in rustc_middle::ty::adt - Rust, for example. The AdtDefData in rustc_middle::ty::adt - Rust could just have a bunch of move-only is_fundamental: bool & move-only is_non_exhaustive: bool & such, rather than needing the extra type, if the compiler would pack the bools together automatically. But the type doesn't want to be fully packed -- certainly taking references to variants: IndexVec<VariantIdx, VariantDef> is something you want to be able to do.

So sure, it could be #[repr(really_packed)] struct AdtFlags { â€Ļ }, but I think field-level would be nicer.

1 Like

The enum case could be "just" #[repr(packed)] enum, though obviously this can't be used with Result/Option, unless we start allowing things like #[repr(C)] Result<_, _> as a type.

The boolean fields could be spelled as just

struct AdtFlags {
    #[repr(packed)]
    pub is_fundamental: bool,
    #[repr(packed)]
    pub is_non_exhaustive: bool,

    ..
}

and I bet implementing that behind a feature gate (at least just the making taking references unsafe part, probably not the bitpacking) wouldn't be too difficult. The compiler team might accept an MCP to implement and utilize it internally without a lang RFC (but also might not).

It'd also generally be useful to have #[repr(aligned_packed)] which removes trailing padding from fields (making field references unsafe[1]) but still requires fields to be sufficiently aligned.

On the struct, it'd prevent field reordering, but on fields (currently disallowed) it could be made to allow that field to be packed without preventing reordering.

There's definitely some room for experimentation. Some of which is probably a clear enough design space to experimentally implement just with a second from the compiler team instead of a full RFV process.


  1. Read-only references — although note that's not all &shared references, but only ones that don't reference cells (i.e. implement the implementation-detail unsafe auto trait Freeze) — could actually be safe, but this is a complex restriction that's already problematic in const contexts. ↩ī¸Ž

1 Like

Just to be clear, my pre-RFC is about packed enums. While bit fields are a way to optimize nested enum representation, it's just an implementation detail.

What you're suggesting here is really bit fields. While I would love such a feature, both for better C compatibility and to provide a better story than these crates, that's an entirely separate proposal, complete with its own questions on how to model it.