U30? in enum to save bits

pub enum Foo {
  A(u30),
  B(u30),
  C(u30),
}

Is it possible to define this and have Foo take up only 8 bits? (I am trying to avoid using a u32 & manually doing big manipulations).

Nope, AFAICT bit twiddling is your only option here.

2 Likes

Note that you can define a newtype to contain the bit twiddling:

(untested)

#[derive(Copy,Clone)]
pub struct PackedFoo(u32);

impl From<Foo> for PackedFoo {
    fn from(foo:Foo) {
        let (tag, arg): (u32, u32) = match foo {
            Foo::A(x) => (0, x),
            Foo::B(x) => (1, x),
            Foo::C(x) => (2, x)
        };
        assert!(arg < (1 << 30));
        PackedFoo((tag << 30) | arg)
    }
)

impl From<PackedFoo> for Foo {
    fn from(packed:PackedFoo) -> Self {
        let tag = packed.0 >> 30;
        let arg = packed.0 ^ (tag << 30);
        match tag {
            0 => Self::A(arg),
            1 => Self::B(arg),
            2 => Self::C(arg),
            _ => panic!("Unknown tag!")
        }
    }
}
2 Likes

In fact that enum will take up 1 byte for the discriminant as well as the space for the variant fields. So for a u32 field a value of Foo would take 5 bytes.
Frankly it's very doubtful that the bit twiddling being suggested here has a real payoff unless you're on severely constrained hardware. If you're not on constrained hardware it's a lot easier and faster to just consume the extra 2 bits of space you'd otherwise be saving per enum value.

There will also be 3 bytes of pading to get the u32 alignment right, for a total of 8. That’s a factor of 2, which could be significant if there are a large number of these or memory space is at a premium on the target architecture. In the normal case, however, I agree that simply using the enum is probably the right choice.