Crate packed_struct

source ·
Expand description

Bit-level packing and unpacking for Rust Documentation master


Packing and unpacking bit-level structures is usually a programming tasks that needlessly reinvents the wheel. This library provides a meta-programming approach, using attributes to define fields and how they should be packed. The resulting trait implementations provide safe packing, unpacking and runtime debugging formatters with per-field documentation generated for each structure.


  • Plain Rust structures, decorated with attributes
  • MSB or LSB integers of user-defined bit widths
  • Primitive enum code generation helper
  • MSB0 or LSB0 bit positioning
  • Documents the field’s packing table
  • Runtime packing visualization
  • Nested packed types
  • Arrays of packed structures as fields
  • Reserved fields, their bits are always 0 or 1

Crate-level feature flags

  • std: use the Rust standard library. Default.
  • alloc: use the alloc crate for no_std + alloc scenarios. Requires nightly Rust.
  • use_serde: add serialization support to the built-in helper types.
  • byte_types_64, byte_types_256: enlarge the size of the generated array, byte and bit width types.

Sample usage


packed_struct = "0.10"

Importing the library with the the most common traits and the derive macros

// This is only needed for pre Rust 2018
#[macro_use] extern crate packed_struct;
// Prelude import with the common imports
use packed_struct::prelude::*;

Example of a single-byte structure, with a 3 bit integer, primitive enum and a bool field.

use packed_struct::prelude::*;

pub struct TestPack {
    tiny_int: Integer<u8, packed_bits::Bits::<3>>,
    #[packed_field(bits="3..=4", ty="enum")]
    mode: SelfTestMode,
    enabled: bool

#[derive(PrimitiveEnum_u8, Clone, Copy, Debug, PartialEq)]
pub enum SelfTestMode {
    NormalMode = 0,
    PositiveSignSelfTest = 1,
    NegativeSignSelfTest = 2,
    DebugMode = 3,

fn main() -> Result<(), PackingError> {
    let test = TestPack {
        tiny_int: 5.into(),
        mode: SelfTestMode::DebugMode,
        enabled: true

    // pack into a byte array
    let packed: [u8; 1] = test.pack()?;
    assert_eq!([0b10111001], packed);

    // unpack from a byte array
    let unpacked = TestPack::unpack(&packed)?;
    assert_eq!(*unpacked.tiny_int, 5);
    assert_eq!(unpacked.mode, SelfTestMode::DebugMode);
    assert_eq!(unpacked.enabled, true);

    // or unpack from a slice
    let unpacked = TestPack::unpack_from_slice(&packed[..])?;


Packing attributes


use packed_struct::prelude::*;

#[packed_struct(attr1="val", attr2="val")]
pub struct Structure {
    #[packed_field(attr1="val", attr2="val")]
    field: u8

Per-structure attributes

size_bytes1 … nSize of the packed byte stream
bit_numberingmsb0 or lsb0Bit numbering for bit positioning of fields. Required if the bits attribute field is used.
endianmsb or lsbDefault integer endianness

Per-field attributes

bits0, 0..1, …Position of the field in the packed structure. Three modes are supported: a single bit, the starting bit, or a range of bits. See details below.
bytes0, 0..1, …Same as above, multiplied by 8.
size_bits1, …Specifies the size of the packed structure. Mandatory for certain types. Specifying a range of bits like bits="0..2" can substite the required usage of size_bits.
size_bytes1, …Same as above, multiplied by 8.
element_size_bits1, …For packed arrays, specifies the size of a single element of the array. Explicitly stating the size of the entire array can substite the usage of this attribute.
element_size_bytes1, …Same as above, multiplied by 8.
tyenumPacking helper for primitive enums.
endianmsb or lsbInteger endianness. Applies to u16/i16 and larger types.

Bit and byte positioning

Used for either bits or bytes on fields. The examples are for MSB0 positioning.

0A single bit or byte
0.., 0:The field starts at bit zero
0..2Exclusive range, bits zero and one
0:1, 0..=1Inclusive range, bits zero and one

More examples

Mixed endian integers

use packed_struct::prelude::*;

pub struct EndianExample {
    int1: u16,
    int2: i32

fn main() -> Result<(), PackingError> {
    let example = EndianExample {
        int1: 0xBBAA,
        int2: 0x11223344

    let packed = example.pack()?;
    assert_eq!([0xAA, 0xBB, 0x11, 0x22, 0x33, 0x44], packed);

24 bit LSB integers

use packed_struct::prelude::*;

pub struct LsbIntExample {
    int1: Integer<u32, packed_bits::Bits::<24>>,

fn main() -> Result<(), PackingError> {
    let example = LsbIntExample {
        int1: 0xCCBBAA.into()

    let packed = example.pack()?;
    assert_eq!([0xAA, 0xBB, 0xCC], packed);

Nested packed types

use packed_struct::prelude::*;
#[derive(PackedStruct, Debug, PartialEq)]
pub struct Duration {
    minutes: u8,
    seconds: u8,
#[derive(PackedStruct, Debug, PartialEq)]
pub struct Record {
    span: Duration,
    events: u8,
fn main() -> Result<(), PackingError> {
    let example = Record {
        span: Duration {
            minutes: 10,
            seconds: 34,
        events: 3,
    let packed = example.pack()?;
    let unpacked = Record::unpack(&packed)?;
    assert_eq!(example, unpacked);

Nested packed types within arrays

use packed_struct::prelude::*;

#[derive(PackedStruct, Default, Debug, PartialEq)]
pub struct TinyFlags {
    _reserved: ReservedZero<packed_bits::Bits::<4>>,
    flag1: bool,
    val1: Integer<u8, packed_bits::Bits::<2>>,
    flag2: bool

#[derive(PackedStruct, Debug, PartialEq)]
pub struct Settings {
    values: [TinyFlags; 4]

fn main() -> Result<(), PackingError> {
    let example = Settings {
        values: [
            TinyFlags { flag1: true,  val1: 1.into(), flag2: false, .. TinyFlags::default() },
            TinyFlags { flag1: true,  val1: 2.into(), flag2: true,  .. TinyFlags::default() },
            TinyFlags { flag1: false, val1: 3.into(), flag2: false, .. TinyFlags::default() },
            TinyFlags { flag1: true,  val1: 0.into(), flag2: false, .. TinyFlags::default() },

    let packed = example.pack()?;
    let unpacked = Settings::unpack(&packed)?;

    assert_eq!(example, unpacked);

Primitive enums with simple discriminants

Supported backing integer types: u8, u16, u32, u64, i8, i16, i32, i64.

Explicit or implicit backing type:

use packed_struct::prelude::*;

#[derive(PrimitiveEnum, Clone, Copy, PartialEq, Debug)]
pub enum ImplicitType {
    VariantMin = 0,
    VariantMax = 255
#[derive(PrimitiveEnum_i16, Clone, Copy)]
pub enum ExplicitType {
    VariantMin = -32768,
    VariantMax = 32767
use packed_struct::PrimitiveEnum;
let t = ImplicitType::VariantMin;
let tn: u8 = t.to_primitive();
assert_eq!(0, tn);

let t = ImplicitType::from_primitive(255).unwrap();
assert_eq!(ImplicitType::VariantMax, t);

Primitive enum packing with support for catch-all unknown values

use packed_struct::prelude::*;
#[derive(PrimitiveEnum_u8, Debug, Clone, Copy)]
pub enum Field {
    A = 1,
    B = 2,
    C = 3
#[derive(PackedStruct, Debug, PartialEq)]
pub struct Register {
    #[packed_field(bits="0..4", ty="enum")]
    field: EnumCatchAll<Field>


Helper structures for runtime packing visualization.
The derivation macros for packing and enums.
Re-exports the most useful traits and types. Meant to be glob imported.
Implementations and wrappers for various packing types.
Tuples of types that can be packed together. Only byte-sized structures can be chained together.


A wrapper for primitive enums that supports catching and retaining any values that don’t have defined discriminants.
Packing errors that might occur during packing or unpacking


A structure that can be packed and unpacked from a byte array.
Infos about a particular type that can be packaged.
A structure that can be packed and unpacked from a slice of bytes.
An enum type that can be packed or unpacked from a simple primitive integer.
Dynamic display formatters.
Static display formatters.

Type Definitions