Expand description
A type-safe and ergonomic library for working with bitfields.
A bitfield is defined using the #[bitfield] attribute macro and must specify either
repr(primitive), where primitive is an unsigned integer type, or container(T), where T
implements the BitContainer trait.
The generated bitfield struct provides access to its fields through the methods of the BitField trait.
Each field type must implement FieldType, which defines its default size and the type used
for storage. The default size can be overridden with the #[bits(N)] attribute on individual fields.
The macro also allows customizing the generated struct: any trait can be forwarded via derives,
while only certain common traits can be requested via impls.
See the #[bitfield] documentation for all available macro arguments and options.
§Example: basic usage
use fray::{bitfield, BitField};
#[bitfield(repr(u8), impls(debug), derives(Clone, Copy))]
pub struct DeviceFlags {
powered_on: bool,
error: bool,
tx_enabled: bool,
rx_enabled: bool,
#[bits(3)]
priority: u8,
#[bits(1)]
reserved: (),
}
fn main() {
let mut flags = DeviceFlags::new();
flags
.with::<powered_on>(true)
.with::<tx_enabled>(true)
.with::<priority>(5);
assert!(flags.get::<powered_on>());
assert!(flags.get::<tx_enabled>());
assert_eq!(flags.get::<priority>(), 5);
assert!(!flags.get::<error>());
flags.set::<error>(true);
assert!(flags.get::<error>());
let debug_fmt = format!("{:?}", flags);
let expected = "DeviceFlags { powered_on: true, error: true, tx_enabled: true, rx_enabled: false, priority: 5, reserved: () }";
assert_eq!(debug_fmt, expected);
let flags_copy = flags;
// DeviceFlags use LSB0 ordering, so the literal is written
// with the least-significant bit on the right.
assert_eq!(flags.into_inner(), 0b0_101_0_1_1_1);
// | | | | | |
// | | | | | powered_on
// | | | | error
// | | | tx_enabled
// | | rx_enabled
// | priority
// reserved
}§Example: custom field type
use fray::{bitfield, BitField, FieldType};
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum PriorityLevel {
Low = 0,
Medium = 1,
High = 2,
Critical = 3,
}
// Requiered for try_get
impl TryFrom<u8> for PriorityLevel {
type Error = u8;
fn try_from(value: u8) -> Result<Self, Self::Error> {
Ok(match value {
0 => Self::Low,
1 => Self::Medium,
2 => Self::High,
3 => Self::Critical,
value => return Err(value)
})
}
}
// Requiered for set/with
impl From<PriorityLevel> for u8 {
fn from(value: PriorityLevel) -> Self {
value as u8
}
}
// Requiered for being accepted as field type in struct declaration
impl FieldType for PriorityLevel {
const SIZE: usize = 3;
type BitsType = u8;
}
#[bitfield(repr(u8))]
pub struct DeviceFlags {
powered_on: bool,
error: bool,
tx_enabled: bool,
rx_enabled: bool,
priority: PriorityLevel,
#[bits(1)]
reserved: (),
}
fn main() {
let mut flags = DeviceFlags::new();
flags.with::<powered_on>(true)
.with::<priority>(PriorityLevel::High)
.with::<error>(true);
assert!(flags.get::<powered_on>());
assert!(flags.get::<error>());
assert_eq!(flags.try_get::<priority>(), Ok(PriorityLevel::High));
}§Example: nested
use fray::{BitFieldImpl, BitField, FieldType, bitfield};
// https://datatracker.ietf.org/doc/html/rfc9293#name-header-format
#[bitfield(repr(u16), bitorder(msb0))]
pub struct TcpHeader {
#[bits(4)]
DOffset: u8,
#[bits(4)]
Rsrvd: (),
flags: ControlBits,
}
#[bitfield(repr(u8), bitorder(msb0))]
pub struct ControlBits {
CWR: bool,
ECE: bool,
URG: bool,
ACK: bool,
PSH: bool,
RST: bool,
SYN: bool,
FIN: bool,
}
impl FieldType for ControlBits {
const SIZE: usize = 8;
type BitsType = u8;
}
impl From<ControlBits> for u8 {
fn from(value: ControlBits) -> Self {
value.into_inner()
}
}
impl From<u8> for ControlBits {
fn from(value: u8) -> Self {
<Self as BitFieldImpl>::Container::from(value).into()
}
}
fn main() {
let mut control_bits = ControlBits::new();
control_bits.with::<FIN>(true).with::<ACK>(true);
let mut tcp_header = TcpHeader::new();
tcp_header.set::<flags>(control_bits);
tcp_header.set::<DOffset>(8);
let flags = tcp_header.get::<flags>();
assert!(flags.get::<FIN>());
assert!(flags.get::<ACK>());
assert_eq!(tcp_header.get::<DOffset>(), 8);
assert_eq!(tcp_header.into_inner(), 0x8011)
}§How It Works
A BitField is essentially a wrapper around a BitContainer. The BitField trait provides
high-level methods for interacting with the bitfield, while the underlying BitContainer defines
how values are stored and retrieved at a low level.
To interact with individual fields, each field must also be defined and implement the
Field<T> trait (where T is the type of the bitfield).
The BitField trait is sealed and is automatically implemented for all types that implement
BitFieldImpl.
The #[bitfield] attribute macro generates a struct that implements BitField,
as well as structs implementing Field for each of its fields.
Modules§
- bitorder
- Module defining bit numbering orders used by bitfields.
- debug
- Utilities for customizing debug output.
- iterable
- Contain the default
BitContainerimplementation.
Traits§
- BitContainer
- A bit container type used by a
BitField, abstracting how bits are stored and retrieved. - BitContainer
For - Provides typed storage and retrieval for a specific type
Twithin aBitContainer. - BitField
- Provides all the convenience methods for interacting with a bitfield structure.
- BitField
Impl - Internal trait required by
BitField. - Field
- Describe a single field within a
BitField. - Field
Type - Describes a type that can be stored in a
BitFieldvia aField.
Attribute Macros§
- bitfield
- Attribute macro for defining bitfield structures.