1 unstable release
Uses new Rust 2024
| new 0.1.0 | Mar 9, 2026 |
|---|
#474 in Procedural macros
37KB
824 lines
capos-bitstruct: Rust structures with bitfields.
bitstruct! is a procedure macro that enables the definition of Rust
structures whose integer or boolean typed fields may be subdivided into
bitfields. bitstruct! is compatible with any struct representation supported
by Rust.
Rust has many packages designed to add bitfield-like behavior to the language. From the perspective of someone
Overview
The macro passes the rust structure through to Rust without the bitfield definitions, but implements getters, setters, and stream-style setters for the bitfields and their associated structure fields. Signed value types are appropriately sign-extended by the getter.
Here's an excerpted example:
bitstruct! {
struct MultiField {
pub cw: u32 { // The control word has bitfields
swizzled: bool 1, // single bit field at bit 0
capType: u8 5, // 5 bit field at bit 1
_: u8 2 = 0, // 2 bit "hole" at bit 6
restrictions: u8 5, // value accepted/returned as u8
},
float_value: f32, // no bitfields => ordinary structure field
}
}
Types given at the bitfield level indicate the value type used by the accessors
and mutators, which may be a bool, any fixed-width integer type not larger
than the containing field type or numeric enum types that implement the
core::convert::From trait. Boolean bitfields occupy a single bit.
For each subdivided structure field, one or more bitfield definition blocks may be specified. Each bitfield definition block specifies in sequence, from least significant bit to most significant bit, a non-overlapping subdivision of the containing field. Bits to be skipped are specified by using '_' as the field identifier. Additional blocks may be provided to specify other possible subdivisions of the containing field.
So, for example, an x86 data register might be described by:
bitstruct! {
/// x86 data register layout
struct X86DataReg {
pub eax: u32 {
al: u8 8,
ah: u8 8,
}, { // Separate block because this overlaps ah, al:
ax: u16 16,
},
}
}
Initializers
Initializers can be provided for any bifield defined in the first bitfield
block. These are gathered by the proc-macro into a stream-style method
init_bitstruct() that can be called from new() or from your implementation
of the Default trait. init_bitstruct() first zeros the containing structure
field and then sets the defaults given for the individual fields. Bitfields
lacking a specified default value are initialized to zero.
Initializers are only gathered for bitfields and the structure fields that
contain them. If no initializers are provided, the containing structure field is
initialized to zero if the init_bitstruct() function is called.
It is unspecified how the initialization is accomplished within
init_bistruct(). Logically, the struct field is initialized first, and then
any bitfields in the first bitfield block are initialized. Given:
bitstruct! {
/// x86 data register layout
#[repr(C)]
struct X86DataReg {
pub eax: u32 {
al: u8 8 = 0x01,
ah: u8 8 = 0x11,
}, { // Separate block because this overlaps ah, al:
ax: u16 16,
},
}
}
It is as if the initializers were performed by:
self
.set_eax(0)
.set_al(0x01)
.set_ah(0x11)
and the eventual eax value will be 0x00001101.
Reserved and Undefined Fields
Hardware vendors sometimes decide to reserve certain bitfields and specify their
values. This can be captured in a bitstruct with an initialized pad field in
the first initializer block. This should be done in the first initializer block.
Multiple pags can be declared.
Hardware vendors who have done a less diligent job of studying how good
specification is done are sometimes prone to define a bit sub-range as
undefined, meaning they won't tell you what the value should be but you return
whatever value they gave you when re-loading the applicable hardware register.
Best practice in this case is to define the field holding this value using a
type that matches the register type, initialize the structure using the value
retrieved from the register, and declare a bitfield block using the (re-useable) name
UNDEFINED to mark where these bits appear.
The 80386 EFLAGS register uses both practices:
bitstruct! {}
#[allow(non_snake_case)]
#[repr(C)]
struct EFLAGS {
pub eflags: u32 {
CF: u32 1,
_ u32 1 = 1, // Intel-specified value
PF: u32 1,
UNDEFINED: u32 1, // Intel does not say
AF: u32 1,
UNDEFINED: u32 1,
ZF: u32 1,
SF: u32 1,
TF: u32 1,
IF: u32 1,
DF: u32 1,
OF: u32 1,
IOPL: u32 2,
NT: u32 1,
UNDEFINED: u32 1,
RF: u32 1,
VM: u32 1,
UNDEFINED: u32 14,
}, {
flags: u32 16, // legacy name on 80286
}
}
}
In this example the snake_case requirement has been enabled to allow the bitfield names to match Intel's documentation exactly.
Excluded Features
Many of the alternative bitfield packages offer the option to define fields
starting with the most significant bit (MSB) rather than the least significant
bit (LSB). The same result can be had in a bitstruct by ensuring that all bits
are specified in the bitfield block and then reversing their order of
occurrence.
Missing Features
At the moment, bitstruct! cannot be applied to anonymous struct definitions.
The option to provide multiple bitfield blocks offers some of the flavor of a
union type. Our original use-case for bitstruct actually uses it in this way. I
expect that we will implement a mechanism for such guards in a future release of
bitstruct.
Alternative Crates to Consider
A search on crates.io for "bitfield" will return half a dozen or more. In all of the cases I have exaamined, the approach taken by these crates is to define bitfields over a single integer at a time. Several are widely used and well regarded.
When a structure has many fields with bit sub-ranges, building them up by
defining seprate structure types for each field can generate a lot of program
text very quickly. Bitstruct can be used that way, but it is designed with a
better localized approach in mind. Ironically the result was simpler to
implement; once the bitsruct! macro has expanded its structure, the result is a
valid Rust structure with an impl block.
Feedback
I receive an overwhelming amount of spam. Please feel free to open issues and discussions on Github, and I will respond as I am able.
Dependencies
~115–485KB
~11K SLoC