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
BitContainer
implementation.
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
T
within 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
BitField
via aField
.
Attribute Macros§
- bitfield
- Attribute macro for defining bitfield structures.