Cloud Parts

binrw

A Rust derive macro that generates a reader and a writer for a struct or enum from directives describing its binary layout, reading from any seekable stream.

What it is
Binary parsing and encoding (declarative binary layouts)
Type
Open source
Made by
Open-source project by jam1garner, Colin Snover, Kitlith and roblabla
License
MIT
Facts checked
9 October 2026

What it is

You put #[binrw] (or #[derive(BinRead, BinWrite)]) on a type and describe its layout with directives in #[br(...)] for reading, #[bw(...)] for writing and #[brw(...)] for both: big or little for byte order, magic = b"DOG" for fixed leading bytes, count = n for arrays, assert(...) for validation, align_before and padding, calc to compute a field on write, temp for a field that is read but not kept, and parse_with for a hand-written reader. Directives are written as Rust expressions rather than strings, so they can name earlier fields and your editor checks them. Built-in helpers cover common patterns: NullString for C strings, FilePtr for a field that holds an offset to data elsewhere in the file, and PosValue to remember where a value was found.

binrw reads from anything that implements io::Read + io::Seek and writes to io::Write + io::Seek, so a file, a socket wrapped in NoSeek or a buffer in memory all work, and a BufReader wrapper speeds up reads from disk. The struct in memory is an ordinary Rust value: binrw never casts raw bytes into a struct, so it needs no #[repr(C)] and has no risk of undefined behaviour from misaligned data. It reads whole bytes; for fields narrower than a byte, the docs point to bit-field crates such as bilge or modular-bitfield, or you split the byte yourself.

Why teams pick it

  • Directives cover the routine parts of binary formats: magic numbers, byte order, padding and alignment, assertions, offsets and counts
  • Directive arguments are real Rust expressions, so they get type checking and IDE support
  • Since 0.10, the short read() and write() calls only compile for types that declare a byte order, so a format can’t silently follow the host CPU’s endianness
  • FilePtr and seek directives handle formats that store offsets to data elsewhere in the file

Watch out for

  • No fields narrower than a byte: bit flags need another crate or manual shifting, unlike deku or Construct
  • Expects a seekable stream; reading from a pipe or socket needs the NoSeek wrapper
  • Still 0.x since 2020, and minor releases break APIs: 0.10 renamed write_to to write, removed Endian::Native and changed the NullString API
  • Reads into owned Rust values rather than zero-copy views of the input, so large byte arrays are copied out of the buffer

Alternatives

What all declarative binary layouts have in common →

How it fits with other technologies

A sensor packet with a version check and a count computed on write · rust

use binrw::{binrw, io::Cursor, BinRead, BinWrite};

// "SN" | version: 4 bits | flags: 4 bits | sensor id: u16 | count: u8 | count × i16
#[binrw]
#[brw(big, magic = b"SN")]
#[derive(Debug)]
struct Reading {
    #[br(temp, assert(vf >> 4 == 1, "unsupported version {}", vf >> 4))]
    #[bw(calc = (version << 4) | flags)]
    vf: u8, // binrw reads whole bytes; split the nibbles by hand
    #[br(calc = vf >> 4)]
    #[bw(ignore)]
    version: u8,
    #[br(calc = vf & 0x0F)]
    #[bw(ignore)]
    flags: u8,
    sensor_id: u16,
    #[br(temp)]
    #[bw(try_calc = u8::try_from(temps.len()))] // written from the Vec, never stale
    count: u8,
    #[br(count = count)]
    temps: Vec<i16>, // hundredths of a degree
}

fn main() {
    let bytes = [b'S', b'N', 0x12, 0x00, 0x07, 0x02, 0x08, 0x34, 0xFF, 0x9C];
    let mut r = Reading::read(&mut Cursor::new(&bytes)).unwrap();
    println!("{r:?}");
    // Reading { version: 1, flags: 2, sensor_id: 7, temps: [2100, -100] }

    r.temps.push(2250);
    let mut out = Cursor::new(Vec::new());
    r.write(&mut out).unwrap();
    println!("{:02x?}", out.into_inner());
    // [53, 4e, 12, 00, 07, 03, 08, 34, ff, 9c, 08, ca]

    let v2 = [b'S', b'N', 0x22, 0x00, 0x07, 0x00];
    println!("{}", Reading::read(&mut Cursor::new(&v2)).unwrap_err());
}

See it in a real stack

No architecture uses binrw yet. Drop it into the playground and wire it up yourself.Open playground →
Where it came from2020–2025

binrw started as binread, a crate by jam1garner “for helping parse structs from binary data using macro magic”. binread 1.0 reached crates.io in April 2020 and could only read: a program that also had to write the format back still needed a second, hand-written serializer.

binrw, first published in September 2020, is the same idea in both directions, and binread’s README now tells users to upgrade because binrw “is the same thing but supports both reads and writes”. Colin Snover, Kitlith and roblabla are now listed as authors alongside jam1garner. Version 0.10, in September 2022, finished the writing side with full documentation, added NoSeek for streams that can’t seek and BufReader for faster file reads, and made byte order explicit for the convenience functions.

Why it’s built this way

Directives as code, not strings
Attribute arguments are parsed as Rust expressions, so they can refer to earlier fields by name and get the compiler’s and the IDE’s checks.
Never transmute bytes into structs
Unlike zero-copy libraries, the in-memory struct doesn’t have to match the raw data, which avoids undefined behaviour and slow unaligned access on some architectures.
Standard Read + Seek streams
Data can come from memory, disk or the network, and buffering or decompression can be layered underneath without binrw knowing.
Make byte order explicit
Since 0.10 the convenience functions without a byte-order argument only work for types that declare one, which prevents unintentional architecture-specific byte ordering.

Timeline

  1. 2020

    binread 1.0 (April); binrw 0.1 adds writing (September)

  2. 2021

    binread 2.2, its last release; users move to binrw

  3. 2022

    0.10: documented writing, NoSeek, BufReader, explicit byte order

  4. 2025

    0.15 (May) reduces generated code to speed up compiles

Sources5 · facts checked 9 October 2026

Improve this page on GitHub