If you have the following struct of booleans, Rust Analyzer says it takes up 3 bytes because there’s three fields.
struct Mask {
field_1: bool,
field_2: bool,
field_3: bool,
} // size = 3, align = 1
- Why doesn’t the compiler optimize this to a 1-byte bitmask?
- How can I make the struct a true bitmask?
As far as I can tell the APIs will all be identical (e.g., getting/setting the fields + methods).
0000_0000
^^^ field_1
|L_ field_2
L__ field_3
P.S. I realize the modular_bitfield crate can make the Mask struct into a true bitmask, but it seems like overkill to use an external crate for something simple like this.
>Solution :
Because bitmasks can actually be slower and more complex than byte-level manipulation. There is no "read the 3rd byte from location X in memory" instruction. If your struct is stored as three bytes, starting at position X and you want to access field_2, then you simply have to access position X+1 in memory. That’s easy, and that’s one instruction on modern systems.
If your struct is stored as three bits and you want to access field_2, then you have to access the whole bitmask (at position X), load it into a register, and then do a bitwise my_value & 2 to get the value you want. And then, if you want it as a normalized Boolean (0 and 1, as opposed to 0 and "some undetermined nonzero value"), you have to bitshift that as well. That’s two or three instructions per access, and the same will be true of modifications. There’s hidden complexity.
And even if we were willing to pay that cost, it would completely redefine Rust’s reference semantics. At the kernel level, you can’t address an individual bit of memory; you can only have pointers to bytes. So you wouldn’t be able to take a reference to field_2 or field_3 since they’re not byte-aligned (and the reference to field_1 would be a reference to the whole struct). So Rust’s references would have to be some form of "fat pointer", storing an OS pointer together with some offset data, which would add a ton of complexity to the already complex borrowing mechanics of Rust.
I am not aware of any built-in Rust way to do this. Zig supports this, with packed struct, but in Rust I believe you’ll end up pulling in an external library. If it truly is a struct of 3 bytes (as opposed to a more complex example that you’ve distilled down for us), then I’d say let Rust do its thing and forget about the bit packing. But if you’ve benchmarked this and it really is a bottleneck, then look into some external crates.