A Rust crate for helping parse binary data using ✨macro magic✨.
Example
# use binread::{prelude::*, io::Cursor, NullString};
#[derive(BinRead)]
#[br(magic = b"DOG", assert(name.len() != 0))]
struct Dog {
bone_pile_count: u8,
#[br(big, count = bone_pile_count)]
bone_piles: Vec<u16>,
#[br(align_before = 0xA)]
name: NullString
}
let mut reader = Cursor::new(b"DOG\x02\x00\x01\x00\x12\0\0Rudy\0");
let dog: Dog = reader.read_ne().unwrap();
assert_eq!(dog.bone_piles, &[0x1, 0x12]);
assert_eq!(dog.name.into_string(), "Rudy")
The Basics
At the core of binread
is the BinRead
trait. It defines how to read
a type from bytes and is already implemented for most primitives and simple collections.
use ;
let mut reader = new;
let val = u32 read.unwrap;
However, read
is intentionally simple and, as a result, doesn't even
allow you to configure the byte order. For that you need read_options
which, while more powerful, isn't exactly ergonomics.
So, as a balance between ergonomics and configurability you have the BinReaderExt
trait. It is an extension for readers to allow for you to directly read any BinRead types from
any reader.
Example:
use ;
let mut reader = new;
let val: u16 = reader.read_be.unwrap;
assert_eq!;
It even works for tuples and arrays of BinRead types for up to size 32.
Derive Macro
The most significant feature of binread is its ability to use the Derive macro to
implement BinRead
for your own types. This allows you to replace repetitive
imperative code with declarative struct definitions for your binary data parsing.
Basic Derive Example
# use BinRead;
// Also works with tuple types!
;
Attributes
The BinRead derive macro uses attributes in order to allow for more complicated parsers. For
example you can use big
or little
at either the struct-level or the field-level in order
to override the byte order of values.
# use ;
] u32, // will be big endian
u32, // will be little endian
);
The order of precedence is: (from highest to lowest)
- Field-level
- Variant-level (for enums)
- Top-level
- Configured (i.e. what endianess was passed in)
- Native endianess
For a list of attributes see the attribute
module
Generics
The BinRead derive macro also allows for generic parsing. That way you can build up higher-level parsers that can have their type swapped out to allow greater reuse of code.
# use ;
In order to parse generically, we have to (in some way) bound Args
. The easiest way to do
this is to bound <T as BinRead>::Args
to ()
(no arguments), however it is also possible to
either accept a specific set of arguments or be generic over the given arguments.