🌻 Bitfields
A Rust create that provides a procedural macro for generating bitfields from structs or custom types, which is useful for defining schemas when working with low-level environments or concepts (e.g. embedded or writing an emulator).
- Efficient and safe code like you would write by hand.
- Fully flexible and customizable, you can choose what gets generated.
- No unsafe code, zero-allocations, const functions, and no runtime dependencies.
- Constant memory usage regardless of fields as the size of the bitfield is constant to its type.
- Usable in
no_std
environments. - Compile-time checks for fields, types, and bits bounds checking.
- Supports most primitive and user-defined custom types.
- Supports endian conversion (little, big) and field order (msb or lsb).
- Generates checked versions of field setters to catch overflows.
🔧 Usage
Add the following to your Cargo.toml
:
[]
= "0.1.0"
🚀 Getting Started
You're emulating the N1nt3nd0 GameChild and come across the Display Control Register (DISPCNT) which is an 8-bit register:
Bit Expl.
0-1 BG Mode (0-7=Video Mode)
2-3 Display BG (0-2) (0=BGx Off, 1=BGx On)
4 OBJ Character VRAM Mapping (0=Two dimensional, 1=One dimensional)
5-7 Always 0x3 Padding
In table form, the bits are as follows:
You can define the register as follows:
use bitfield;
/// Create a struct annotated with the #[bitfield] attribute.
/// By default, the field order is the least significant bit
/// to most significant bit.
// Attributes are passed to the struct.
/// Define a custom type that represents the display mode.
/// Implement the `from_bits` and `into_bits` funcs for the custom type.
// Creating the display mode custom type.
let display_mode = DisplayMode ;
// Building the display control.
let display_control = new
.with_bg_mode
.with_display_mode
.with_obj_char_vram_mapping
.build;
// Converting into bits.
let val = display_control.into_bits;
assert_eq!;
🤔 What Other Features Does Bitfields Offer?
Bitfields offers a wide range of features to help you define and work with bitfields.
use bitfield;
/// All fields in the bitfield must sum up to the number of bits of the bitfield type.
/// Custom types must have 2 const functions, `from_bits` and `into_bits` to convert
/// the type to and from bits functions.
// Usage:
// Creates a new bitfield using a builder pattern, unset fields default to 0
// or their provided default value.
let mut bitfield = new
.with_u8int
.with_small_u8int
.with_custom_type
// .with_custom_type(CustomType::default()) // Can pass a [`CustomType`] instance.
// .with_read_only(0x3) // Compile error, read-only field can't be set.
// .with__padding(0x3) // Compile error, padding fields are inaccessible.
.with_signed_int
.with_small_signed_int
.build;
// let bitfield = Bitfield::new(); // Bitfield with default values.
// let bitfield = Bitfield::new_without_defaults(); // Bitfield without default values.
// let bitfield = BitfieldBuilder::new_without_defaults(); // Builder without defaults.
// Accessing fields:
let u8int = bitfield.u8int; // Getters
let small_u8int = bitfield.small_u8int; // Signed-types are sign-extended.
// Setting fields:
bitfield.set_u8int; // Setters
bitfield.checked_set_small_u8int; // Checked setter, error if value overflow bits.
// Converting to bits:
let bits = bitfield.into_bits;
// Converting from bits:
let bitfield = from_bits; // Converts from bits
// let bitfield = Bitfield::from_bits_with_defaults(0x3); // Converts, respects defaults.
// Constants:
assert_eq!; // Number of bits of the field.
assert_eq!; // The offset of the field in the bitfield.
Bitfield Types
A bitfield can represent unsigned types (u8
, u16
, u32
, u64
, u128
) up to
128-bits, because Rust was weak and stopped at u128
. The field bits of a bitfield
must add up to the number of bits of the bitfield type.
use bitfield;
Bitfield Field Types
A bitfield field can be any unsigned (u8
, u16
, u32
, u64
, u128
), signed
type (i8
, i16
, i32
, i64
, i128
), or a custom type that implements the
const functions from_bits
and into_bits
.
use bitfield;
let bitfield = new;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
Constructing a Bitfield
A bitfield can be constructed using the new
and new_without_defaults
constructors. The former initializes
the bitfield with default values, while the latter initializes the bitfield without default values,
except for padding fields which always keep their default value or 0.
use bitfield;
let bitfield = new;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
let bitfield_without_defaults = new_without_defaults;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
Bitfield Conversions
A bitfield can be converted from bits using the from_bits
or from_bits_with_defaults
functions. The former
ignores default values, while the latter respects them. Padding fields are always 0 or their default value. The
bitfield can also be converted to bits using the into_bits
function. The From
trait is also implemented
between the bitfield and the bitfield type and operates the same as from_bits
.
use bitfield;
let bitfield = from_bits;
assert_eq!;
assert_eq!;
assert_eq!;
let val = bitfield.into_bits;
assert_eq!;
let bitfield_respect_defaults = from_bits_with_defaults;
assert_eq!; // Default value respected
assert_eq!;
assert_eq!;
let val = bitfield_respect_defaults.into_bits;
assert_eq!;
// From trait
let val: u32 = bitfield.into;
assert_eq!;
let bitfield: Bitfield = val.into;
assert_eq!;
Conversion Endianess
Sometimes the outside world is outside our control, like how systems store or expect data endian. Luckily, the endian
of the bitfield conversions can be controlled by specifying the #[bitfield(from_endian = x, into_endian = x)]
args.
The possible endians are little
or big
. By default, the endian of both is big
.
use bitfield;
// We are working with a system that stores data in little-endian, we
// set the from_endian to little for the proper representation.
//
// The system expects the data it stores in big-endian, we set the
// into_endian to big-endian for converting into the proper representation.
// The host device stored the data 0x12345678 in little-endian memory
// as [0x78, 0x56, 0x34, 0x12].
let bitfield = from_bits;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
Field Order
By default, fields are ordered from the least significant bit (lsb) to the most significant bit (msb).
The order can be changed by specifying the #[bitfield(order = x)]
arg on the bitfield struct.
There are two field orderings, lsb
and msb
.
use bitfield;
let bitfield = new;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
let val = bitfield.into_bits;
// .- a
// | .- b
// | | .- c
// | | | .- d
assert_eq!;
assert_eq!; // Offset of the a field in the bitfield.
Field Access
Field access can be controlled by specifying the #[bits(access = x)]
arg on a field. There are four accesses:
rw
- Read and write access (default)ro
- Read-only access.wo
- Write-only access.none
- No access.
use bitfield;
let mut bitfield = new
.with_read_write
// .with_read_only(0x34) // Compile error, read-only field can't be set.
.with_write_only
// .with_none(0x78) // Compile error, none field can't be set.
.build;
bitfield.set_read_write;
// bitfield.set_read_only(0x34); // Compile error, read-only field can't be set.
bitfield.set_write_only;
// bitfield.set_none(0x78); // Compile error, none field can't be set.
assert_eq!;
assert_eq!;
// assert_eq!(bitfield.write_only(), 0x56); // Compile error, write-only can't be read.
// assert_eq!(bitfield.none(), 0xFF); // Compile error, none field can't be accessed.
assert_eq!; // All fields exposed when converted to bits.
Checked Setters
Normally, when a field is set, the value is truncated to the number of bits of the field. Fields also have checked setters that returns an error if the value overflows the number of bits of the field.
use bitfield;
let mut bitfield = new;
bitfield.set_a;
bitfield.set_b; // Truncated to 4 bits.
assert_eq!;
assert_eq!;
let res = bitfield.checked_set_b; // Error, value overflows bits.
assert!;
Padding Fields
Fields prefixed with an underscore _
are padding fields, which are inaccessible. Meaning the field is always
0/false or a default value. They are useful for padding the bits of the bitfield.
use bitfield;
let bitfield = new;
assert_eq!;
// assert_eq!(bitfield._padding(), 0xFF00); // Compile error, padding inaccessible.
// bitfield.set__padding(0xFF); // Compile error, padding fields are inaccessible.
assert_eq!; // All fields exposed when converted to bits.
Field Constants
Fields with read or write access have constants generated for their number of bits and offset in the bitfield.
use bitfield;
assert_eq!; // Number of bits of the afield.
assert_eq!; // The offset of the a field in the bitfield.
assert_eq!; // Number of bits of the b field.
assert_eq!; // The offset of the b field in the bitfield.
assert_eq!; // Number of bits of c the field.
assert_eq!; // The offset of the c field in the bitfield.
assert_eq!; // Number of bits of the d field.
assert_eq!; // The offset of the d field in the bitfield.
Debug Implementation
A debug implementation is generated for the bitfield, which prints the fields and their values.
use bitfield;
let bitfield = new;
assert_eq!;
Passing Attributes
Attributes below the #[bitfield]
attribute are passed to the generated struct.
use bitfield;
Complete Generation Control
You have complete control over what gets generated by the bitfield macro. When your deploying to a resource-constrained
environment, you can generate only the necessary functions or implementations. You can disable generation by passing
false
to its attribute arg.
The #[bitfield]
args that control generation are:
#[bitfield(new = true)]
- Generates thenew
andnew_without_defaults
constructor.#[bitfield(from_bits = true)]
- Generates thefrom_bits
andfrom_bits_with_defaults
functions.#[bitfield(into_bits = true)]
- Generates theinto_bits
function.#[bitfield(from = true)]
- Generates theFrom
trait implementation.#[bitfield(debug = true)]
- Generates theDebug
trait implementation.#[bitfield(default = true)]
- Generates theDefault
trait implementation#[bitfield(builder = true)]
- Generates the builder implementation.
⚖️ License
Distributed under the MIT License. See LICENSE for more information.
🤝 Contributing
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the MIT license, shall be licensed as above, without any additional terms or conditions.
🥶 Employment Disclaimer
As of Dec 2024, I am a Google employee; "Bitfields" is my own work, not affiliated with Google, its subsidiaries, nor endorsing any Google-owned products or tools. "Bitfields" was written without any proprietary knowledge, tools, or resources of Google.
💯 Acknowledgments
- Johan Mickos - Awesome dude who gave me the 'bitfields' crate name.
- dtolnay/proc-macro-workshop
- wrenger/bitfield-struct-rs
- hawkw/mycelium
- dzamlo/rust-bitfield