#bit-field #bitstruct

macro capos-bitstruct

A macro for Rust structures containing bitfields

1 unstable release

Uses new Rust 2024

new 0.1.0 Mar 9, 2026

#474 in Procedural macros

MIT/Apache

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