Expand description
Glossary of directives in binrw attributes (#[br]
, #[bw]
, #[brw]
).
§List of directives
r/w | Directive | Supports* | Description |
---|---|---|---|
rw | align_after | field | Aligns the readerwriter to the Nth byte after a field. |
rw | align_before | field | Aligns the readerwriter to the Nth byte before a field. |
rw | args | field | Passes arguments to another binrw object. |
rw | args_raw | field | Like args , but specifies a single variable containing the arguments. |
rw | assert | struct, field, non-unit enum, data variant | Asserts that a condition is true. Can be used multiple times. |
rw | big | all except unit variant | Sets the byte order to big-endian. |
rw | calc | field | Computes the value of a field instead of reading datausing a field. |
r | count | field | Sets the length of a vector. |
r | dbg | field | Prints the value and offset of a field to stderr . |
r | default | field | An alias for ignore . |
r | err_context | field | Adds additional context to errors. |
rw | if | field | Reads or writesReadsWrites data only if a condition is true. |
rw | ignore | field | For BinRead , uses the default value for a field instead of reading data. For BinWrite , skips writing the field.Uses the default value for a field instead of reading data.Skips writing the field. |
rw | import | struct, non-unit enum, unit-like enum | Defines extra arguments for a struct or enum. |
rw | import_raw | struct, non-unit enum, unit-like enum | Like import , but receives the arguments as a single variable. |
rw | is_big | field | Conditionally sets the byte order to big-endian. |
rw | is_little | field | Conditionally set the byte order to little-endian. |
rw | little | all except unit variant | Sets the byte order to little-endian. |
rw | magic | all | MatchesWrites a magic number. |
rw | map | all except unit variant | Maps an object or value to a new value. |
rw | map_stream | all except unit variant | Maps the readwrite stream to a new stream. |
r | offset | field | Modifies the offset used by a FilePtr while parsing. |
rw | pad_after | field | Skips N bytes after readingwriting a field. |
rw | pad_before | field | Skips N bytes before readingwriting a field. |
rw | pad_size_to | field | Ensures the readerwriter is always advanced at least N bytes. |
r | parse_with | field | Specifies a custom function for reading a field. |
r | pre_assert | struct, non-unit enum, unit variant | Like assert , but checks the condition before parsing. |
rw | repr | unit-like enum | Specifies the underlying type for a unit-like (C-style) enum. |
rw | restore_position | field | Restores the reader’swriter’s position after readingwriting a field. |
r | return_all_errors | non-unit enum | Returns a Vec containing the error which occurred on each variant of an enum on failure. This is the default. |
r | return_unexpected_error | non-unit enum | Returns a single generic error on failure. |
rw | seek_before | field | Moves the readerwriter to a specific position before readingwriting data. |
rw | stream | struct, non-unit enum, unit-like enum | Exposes the underlying readwrite stream. |
r | temp | field | Uses a field as a temporary variable. Only usable with the binread attribute macro. |
r | try | field | Tries to parse and stores the default value for the type if parsing fails instead of returning an error. |
rw | try_calc | field | Like calc , but returns a Result . |
rw | try_map | all except unit variant | Like map , but returns a Result . |
w | write_with | field | Specifies a custom function for writing a field. |
§Terminology
Each binrw attribute contains a comma-separated list of directives. The following terms are used when describing where particular directives are supported:
#[brw(…)] // ← struct
struct NamedStruct {
#[brw(…)] // ← field
field: Type
}
#[brw(…)] // ← struct
struct TupleStruct(
#[brw(…)] // ← field
Type,
);
#[brw(…)] // ← struct
struct UnitStruct;
#[brw(…)] // ← non-unit enum
enum NonUnitEnum {
#[brw(…)] // ← data variant
StructDataVariant {
#[brw(…)] // ← field
field: Type
},
#[brw(…)] // ← data variant
TupleDataVariant(
#[brw(…)] // ← field
Type
),
#[brw(…)] // ← unit variant
UnitVariant
}
#[brw(…)] // ← unit-like enum
enum UnitLikeEnum {
#[brw(…)] // ← unit variant
UnitVariantA,
…
#[brw(…)] // ← unit variant
UnitVariantN
}
§Arguments
Arguments provide extra data necessary for readingwriting an object.
The import
and args
directives define the type of
BinRead::Args
BinWrite::Args
and the values passed in the args
argument of a
BinRead::read_options
BinWrite::write_options
call, respectively.
Any (earlier only, when reading)earlier
field or import can be referenced in args
.
§Ways to pass and receive arguments
There are 3 ways arguments can be passed and received:
- Tuple-style arguments (or “ordered arguments”): arguments passed as a tuple
- Named arguments: arguments passed as an object
- Raw arguments: arguments passed as a type of your choice
§Tuple-style arguments
Tuple-style arguments (or “ordered arguments”) are passed via args()
and
received via import()
:
#[br(import($($ident:ident : $ty:ty),* $(,)?))]
#[br(args($($value:expr),* $(,)?))]
#[bw(import($($ident:ident : $ty:ty),* $(,)?))]
#[bw(args($($value:expr),* $(,)?))]
This is the most common form of argument passing because it works mostly like a normal function call:
#[derive(BinRead)]
#[br(import(val1: u32, val2: &str))]
struct Child {
// ...
}
#[derive(BinRead)]
struct Parent {
val: u32,
#[br(args(val + 3, "test"))]
test: Child
}
#[derive(BinWrite)]
#[bw(import(val1: u32, val2: &str))]
struct Child {
// ...
}
#[derive(BinWrite)]
struct Parent {
val: u32,
#[bw(args(val + 3, "test"))]
test: Child
}
§Named arguments
Named arguments are passed via args {}
and received via import {}
(note the curly braces), similar to a struct literal:
#[br(import { $($ident:ident : $ty:ty $(= $default:expr)?),* $(,)? })]
#[br(args { $($name:ident $(: $value:expr)?),* $(,)? } )]
#[bw(import { $($ident:ident : $ty:ty $(= $default:expr)?),* $(,)? })]
#[bw(args { $($name:ident $(: $value:expr)?),* $(,)? } )]
Field init shorthand and optional arguments are both supported.
Named arguments are particularly useful for container objects like Vec
,
but they can be used by any parserserialiser
that would benefit from labelled, optional, or unordered arguments:
#[derive(BinRead)]
#[br(import {
count: u32,
other: u16 = 0 // ← optional argument
})]
struct Child {
// ...
}
#[derive(BinRead)]
struct Parent {
count: u32,
#[br(args {
count, // ← field init shorthand
other: 5
})]
test: Child,
#[br(args { count: 3 })]
test2: Child,
}
#[derive(BinWrite)]
#[bw(import {
count: u32,
other: u16 = 0 // ← optional argument
})]
struct Child {
// ...
}
#[derive(BinWrite)]
struct Parent {
count: u32,
#[bw(args {
count: *count,
other: 5
})]
test: Child,
#[bw(args { count: 3 })]
test2: Child,
}
When nesting named arguments, you can use the binrw::args!
macro to
construct the nested object:
#[derive(BinRead)]
#[br(import {
more_extra_stuff: u32
})]
struct Inner {
// ...
}
#[derive(BinRead)]
#[br(import {
extra_stuff: u32,
inner_stuff: <Inner as BinRead>::Args<'_>
})]
struct Middle {
#[br(args_raw = inner_stuff)]
inner: Inner
}
#[derive(BinRead)]
struct Outer {
extra_stuff: u32,
inner_extra_stuff: u32,
#[br(args { // ← Middle::Args<'_>
extra_stuff,
inner_stuff: binrw::args! { // ← Inner::Args<'_>
more_extra_stuff: inner_extra_stuff
}
})]
middle: Middle,
}
#[derive(BinWrite)]
#[bw(import {
more_extra_stuff: u32
})]
struct Inner {
// ...
}
#[derive(BinWrite)]
#[bw(import {
extra_stuff: u32,
inner_stuff: <Inner as BinWrite>::Args<'_>
})]
struct Middle {
#[bw(args_raw = inner_stuff)]
inner: Inner
}
#[derive(BinWrite)]
struct Outer {
extra_stuff: u32,
inner_extra_stuff: u32,
#[bw(args { // ← Middle::Args<'_>
extra_stuff: *extra_stuff,
inner_stuff: binrw::args! { // ← Inner::Args<'_>
more_extra_stuff: *inner_extra_stuff
}
})]
middle: Middle,
}
Named arguments can also be used with types that manually implement
BinRead
BinWrite
by creating
a separate type for its arguments that implements binrw::NamedArgs
.
The count
and offset
directives are sugar for setting
the count
and offset
arguments on any named arguments object; see those
directives’ documentation for more information.
§Raw arguments
Raw arguments allow the
BinRead::Args
BinWrite::Args
type to be specified explicitly and to receive all arguments into a single
variable:
#[br(import_raw($binding:ident : $ty:ty))]
#[br(args_raw($value:expr))] or #[br(args_raw = $value:expr)]
#[bw(import_raw($binding:ident : $ty:ty))]
#[bw(args_raw($value:expr))] or #[bw(args_raw = $value:expr)]
They are most useful for argument forwarding:
type Args = (u32, u16);
#[derive(BinRead)]
#[br(import_raw(args: Args))]
struct Child {
// ...
}
#[derive(BinRead)]
#[br(import_raw(args: Args))]
struct Middle {
#[br(args_raw = args)]
test: Child,
}
#[derive(BinRead)]
struct Parent {
count: u32,
#[br(args(1, 2))]
mid: Middle,
// identical to `mid`
#[br(args_raw = (1, 2))]
mid2: Middle,
}
type Args = (u32, u16);
#[derive(BinWrite)]
#[bw(import_raw(args: Args))]
struct Child {
// ...
}
#[derive(BinWrite)]
#[bw(import_raw(args: Args))]
struct Middle {
#[bw(args_raw = args)]
test: Child,
}
#[derive(BinWrite)]
struct Parent {
count: u32,
#[bw(args(1, 2))]
mid: Middle,
// identical to `mid`
#[bw(args_raw = (1, 2))]
mid2: Middle,
}
§Assert
The assert
directive validates objects and fields
after they are read or before they are written,
after they are read,
before they are written,
returning an error if the assertion condition evaluates to false
:
#[br(assert($cond:expr $(,)?))]
#[br(assert($cond:expr, $msg:literal $(,)?)]
#[br(assert($cond:expr, $fmt:literal, $($arg:expr),* $(,)?))]
#[br(assert($cond:expr, $err:expr $(,)?)]
#[bw(assert($cond:expr $(,)?))]
#[bw(assert($cond:expr, $msg:literal $(,)?)]
#[bw(assert($cond:expr, $fmt:literal, $($arg:expr),* $(,)?))]
#[bw(assert($cond:expr, $err:expr $(,)?)]
Multiple assertion directives can be used; they will be combined and executed in order.
Assertions added to the top of an enum will be checked against every variant in the enum.
Any (earlier only, when reading)earlier
field or import can be referenced by expressions
in the directive. When reading, anAn
assert
directive on a struct, non-unit enum, or data variant can access the
constructed object using the self
keyword.
§Examples
§Formatted error
#[derive(BinRead)]
#[br(assert(some_val > some_smaller_val, "oops! {} <= {}", some_val, some_smaller_val))]
struct Test {
some_val: u32,
some_smaller_val: u32
}
let error = Cursor::new(b"\0\0\0\x01\0\0\0\xFF").read_be::<Test>();
assert!(error.is_err());
let error = error.unwrap_err();
let expected = "oops! 1 <= 255".to_string();
assert!(matches!(error, binrw::Error::AssertFail { message: expected, .. }));
#[derive(BinWrite)]
#[bw(assert(some_val > some_smaller_val, "oops! {} <= {}", some_val, some_smaller_val))]
struct Test {
some_val: u32,
some_smaller_val: u32
}
let object = Test { some_val: 1, some_smaller_val: 255 };
let error = object.write_le(&mut Cursor::new(vec![]));
assert!(error.is_err());
let error = error.unwrap_err();
let expected = "oops! 1 <= 255".to_string();
assert!(matches!(error, binrw::Error::AssertFail { message: expected, .. }));
§Custom error
#[derive(Debug, PartialEq)]
struct NotSmallerError(u32, u32);
impl core::fmt::Display for NotSmallerError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{} <= {}", self.0, self.1)
}
}
#[derive(BinRead, Debug)]
#[br(assert(some_val > some_smaller_val, NotSmallerError(some_val, some_smaller_val)))]
struct Test {
some_val: u32,
some_smaller_val: u32
}
let error = Cursor::new(b"\0\0\0\x01\0\0\0\xFF").read_be::<Test>();
assert!(error.is_err());
let error = error.unwrap_err();
assert_eq!(error.custom_err(), Some(&NotSmallerError(0x1, 0xFF)));
#[derive(Debug, PartialEq)]
struct NotSmallerError(u32, u32);
impl core::fmt::Display for NotSmallerError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{} <= {}", self.0, self.1)
}
}
#[derive(BinWrite, Debug)]
#[bw(assert(some_val > some_smaller_val, NotSmallerError(*some_val, *some_smaller_val)))]
struct Test {
some_val: u32,
some_smaller_val: u32
}
let object = Test { some_val: 1, some_smaller_val: 255 };
let error = object.write_le(&mut Cursor::new(vec![]));
assert!(error.is_err());
let error = error.unwrap_err();
assert_eq!(error.custom_err(), Some(&NotSmallerError(0x1, 0xFF)));
§In combination with map
or try_map
use modular_bitfield::prelude::*;
#[bitfield]
#[derive(BinRead)]
#[br(assert(self.is_fast()), map = Self::from_bytes)]
pub struct PackedData {
status: B4,
is_fast: bool,
is_static: bool,
is_alive: bool,
is_good: bool,
}
let data = Cursor::new(b"\x53").read_le::<PackedData>().unwrap();
§Errors
If the assertion fails and there is no second argument, or a string literal
is given as the second argument, an AssertFail
error is returned.
If the assertion fails and an expression is given as the second argument,
a Custom
error containing the result of the
expression is returned.
Arguments other than the condition are not evaluated unless the assertion fails, so it is safe for them to contain expensive operations without impacting performance.
In all cases, the readerwriter’s position is reset to where it was before parsingserialisation started.
§Backtrace
When an error is raised during parsing, BinRead
forms a backtrace, bubbling the
error upwards and attaching additional information (surrounding code, line numbers,
messages, etc.) in order to aid in debugging.
The #[br(err_context(...))]
attribute can work in one of two ways:
-
If the first (or only) item is a string literal, it will be a message format string, with any other arguments being used as arguments. This uses the same formatting as
format!
,println!
, and other standard library formatters. -
Otherwise, only a single argument is allowed, which will then be attached as a context type. This type must implement
Display
,Debug
,Send
, andSync
.
§Example
#[derive(BinRead)]
struct InnerMostStruct {
#[br(little)]
len: u32,
#[br(count = len, err_context("len = {}", len))]
items: Vec<u32>,
}
#[derive(BinRead)]
struct MiddleStruct {
#[br(little)]
#[br(err_context("While parsing the innerest most struct"))]
inner: InnerMostStruct,
}
#[derive(Debug, Clone)] // Display implementation omitted
struct Oops(u32);
#[derive(BinRead)]
struct OutermostStruct {
#[br(little, err_context(Oops(3 + 1)))]
middle: MiddleStruct,
}
§Byte order
The big
and little
directives specify the byte order
of data in a struct, enum, variant, or field:
#[br(big)]
#[br(little)]
#[bw(big)]
#[bw(little)]
The is_big
and is_little
directives conditionally set the byte order of
a struct field:
#[br(is_little = $cond:expr)] or #[br(is_little($cond:expr))]
#[br(is_big = $cond:expr)] or #[br(is_big($cond:expr))]
#[bw(is_little = $cond:expr)] or #[bw(is_little($cond:expr))]
#[bw(is_big = $cond:expr)] or #[bw(is_big($cond:expr))]
The is_big
and is_little
directives are primarily useful when byte order
is defined in the data itself. Any
(earlier only, when reading)earlier
field or import can
be referenced in the condition. Conditional byte order directives can only
be used on struct fields.
The order of precedence (from highest to lowest) for determining byte order within an object is:
- A directive on a field
- A directive on an enum variant
- A directive on the struct or enum
- The
endian
parameter of theBinRead::read_options
BinWrite::write_options
call
However, if a byte order directive is added to a struct or enum, that byte order will always be used, even if the object is embedded in another object or explicitly called with a different byte order:
#[derive(BinRead)]
#[br(little)] // ← this *forces* the struct to be little-endian
struct Child(u32);
#[derive(BinRead)]
struct Parent {
#[br(big)] // ← this will be ignored
child: Child,
};
let endian = Endian::Big; /* ← this will be ignored */
Parent::read_options(&mut Cursor::new(b"\x01\0\0\0"), endian, ())
#[derive(BinWrite)]
#[bw(little)] // ← this *forces* the struct to be little-endian
struct Child(u32);
#[derive(BinWrite)]
struct Parent {
#[bw(big)] // ← this will be ignored
child: Child,
};
let object = Parent { child: Child(1) };
let endian = Endian::Big; /* ← this will be ignored */
let mut output = Cursor::new(vec![]);
object.write_options(&mut output, endian, ())
When manually implementing
BinRead::read_options
BinWrite::write_options
or a
custom parserwriter function,
the byte order is accessible from the endian
parameter.
§Examples
§Mixed endianness in one object
#[derive(BinRead)]
#[br(little)]
struct MyType (
#[br(big)] u32, // ← will be big-endian
u32, // ← will be little-endian
);
#[derive(BinWrite)]
#[bw(little)]
struct MyType (
#[bw(big)] u32, // ← will be big-endian
u32, // ← will be little-endian
);
§Conditional field endianness
#[derive(BinRead)]
#[br(big)]
struct MyType {
val: u8,
#[br(is_little = (val == 3))]
other_val: u16 // ← little-endian if `val == 3`, otherwise big-endian
}
MyType::read(&mut Cursor::new(b"\x03\x01\x00"))
#[derive(BinWrite)]
#[bw(big)]
struct MyType {
val: u8,
#[bw(is_little = (*val == 3))]
other_val: u16 // ← little-endian if `val == 3`, otherwise big-endian
}
let object = MyType { val: 3, other_val: 1 };
let mut output = Cursor::new(vec![]);
object.write(&mut output)
§Calculations
These directives can only be used with binwrite
.
They will not work with #[derive(BinWrite)]
.
When using these directives, the #[binwrite]
attribute must be placed
before other attributes like #[derive(Debug)]
. Otherwise, the other
attributes will generate code that references non-existent fields, and
compilation will fail.
The calc
and try_calc
directives compute the value of a field instead of
reading data from the readerwriting from
a struct field:
#[br(calc = $value:expr)] or #[br(calc($value:expr))]
#[br(try_calc = $value:expr)] or #[br(try_calc($value:expr))]
#[bw(calc = $value:expr)] or #[bw(calc($value:expr))]
#[bw(try_calc = $value:expr)] or #[bw(try_calc($value:expr))]
Any (earlier only, when reading)earlier field or import can be referenced by the expression in the directive.
When using try_calc
, the produced value must be a Result<T, E>
.
Since the field is treated as a temporary variable instead of an actual
field, when deriving BinRead
, the field should also be annotated with
#[br(temp)]
.
§Examples
§Infallible calculation
#[derive(BinRead)]
struct MyType {
var: u32,
#[br(calc = 3 + var)]
var_plus_3: u32,
}
#[binwrite] // ← must be before other attributes that use struct fields
#[bw(big)]
struct MyType {
var: u32,
#[bw(calc = var - 3)]
var_minus_3: u32,
}
let object = MyType { var: 4 };
let mut output = Cursor::new(vec![]);
object.write(&mut output).unwrap();
assert_eq!(output.into_inner(), b"\0\0\0\x04\0\0\0\x01");
§Fallible calculation
#[derive(BinRead)]
struct MyType {
var: u32,
#[br(try_calc = u16::try_from(var + 3))]
var_plus_3: u16,
}
#[binwrite] // ← must be before other attributes that use struct fields
#[bw(big)]
struct MyType {
var: u32,
#[bw(try_calc = u16::try_from(var - 3))]
var_minus_3: u16,
}
let object = MyType { var: 4 };
let mut output = Cursor::new(vec![]);
object.write(&mut output).unwrap();
assert_eq!(output.into_inner(), b"\0\0\0\x04\0\x01");
§Conditional values
The if
directive allows conditional
parsingserialisation of a field,
readingwriting data if the
condition is true and optionally using a computed value if the condition is
false:
#[br(if($cond:expr))]
#[br(if($cond:expr, $alternate:expr))]
#[bw(if($cond:expr))]
#[bw(if($cond:expr, $alternate:expr))]
If an alternate is provided, that value will be used when the condition is
false; otherwise,
for reads, the default
value for
the type will be used, and for writes, no data will be written.
the default
value for the type
will be used.
no data will be written.
The alternate expression is not evaluated unless the condition is false, so it is safe for it to contain expensive operations without impacting performance.
Any (earlier only, when reading)earlier field or import can be referenced by the expression in the directive.
The map
directive can also be used to conditionally
write a field by returning an Option
, where a None
value skips
writing.
§Examples
§Reading an Option
field with no alternate
#[derive(BinRead)]
struct MyType {
var: u32,
#[br(if(var == 1))]
original_byte: Option<u8>,
#[br(if(var != 1))]
other_byte: Option<u8>,
}
§Reading a scalar field with an explicit alternate
#[derive(BinRead)]
struct MyType {
var: u32,
#[br(if(var == 1, 0))]
original_byte: u8,
#[br(if(var != 1, 42))]
other_byte: u8,
}
§Skipping a scalar field with no alternate
#[derive(BinWrite)]
struct Test {
x: u8,
#[bw(if(*x > 1))]
y: u8,
}
let mut output = Cursor::new(vec![]);
output.write_be(&Test { x: 1, y: 3 }).unwrap();
assert_eq!(output.into_inner(), b"\x01");
let mut output = Cursor::new(vec![]);
output.write_be(&Test { x: 2, y: 3 }).unwrap();
assert_eq!(output.into_inner(), b"\x02\x03");
§Writing a scalar field with an explicit alternate
#[derive(BinWrite)]
struct Test {
x: u8,
#[bw(if(*x > 1, 0))]
y: u8,
}
let mut output = Cursor::new(vec![]);
output.write_be(&Test { x: 1, y: 3 }).unwrap();
assert_eq!(output.into_inner(), b"\x01\0");
let mut output = Cursor::new(vec![]);
output.write_be(&Test { x: 2, y: 3 }).unwrap();
assert_eq!(output.into_inner(), b"\x02\x03");
§Count
The count
directive is a shorthand for passing a count
argument to a
type that accepts it as a named argument:
#[br(count = $count:expr) or #[br(count($count:expr))]
It desugars to:
#[br(args { count: $count as usize })]
This directive is most commonly used with Vec
, which accepts count
and inner
arguments through its
associated VecArgs
type.
When manually implementing
BinRead::read_options
or a
custom parser function, the count
value is accessible
from a named argument named count
.
Any earlier field or import can be referenced by the expression in the directive.
§Examples
§Using count
with Vec
#[derive(BinRead)]
struct Collection {
size: u32,
#[br(count = size)]
data: Vec<u8>,
}
§Using count
with a Vec
with arguments for the inner type
#[derive(BinRead)]
#[br(import(version: i16))]
struct Inner(#[br(if(version > 0))] u8);
#[derive(BinRead)]
struct Collection {
version: i16,
size: u32,
#[br(count = size, args { inner: (version,) })]
data: Vec<Inner>,
}
§Custom parserswriters
The parse_with
directive specifies a custom parsing function which can be
used to override the default BinRead
implementation for
a type, or to parse types which have no BinRead
implementation at all:
#[br(parse_with = $parse_fn:expr)] or #[br(parse_with($parse_fn:expr))]
Use the #[parser]
attribute macro to create compatible
functions.
Any earlier field or import can be referenced by the expression in the directive (for example, to construct a parser function at runtime by calling a function generator).
The write_with
directive specifies a custom serialisation function which
can be used to override the default BinWrite
implementation for a type, or to serialise types which have no BinWrite
implementation at all:
#[bw(write_with = $write_fn:expr)] or #[bw(write_with($write_fn:expr))]
Use the #[writer]
attribute macro to create compatible
functions.
Any field or import can be referenced by the expression in the directive (for example, to construct a serialisation function at runtime by calling a function generator).
§Examples
§Using a custom parser to generate a HashMap
#[binrw::parser(reader, endian)]
fn custom_parser() -> BinResult<HashMap<u16, u16>> {
let mut map = HashMap::new();
map.insert(
<_>::read_options(reader, endian, ())?,
<_>::read_options(reader, endian, ())?,
);
Ok(map)
}
#[derive(BinRead)]
#[br(big)]
struct MyType {
#[br(parse_with = custom_parser)]
offsets: HashMap<u16, u16>
}
§Using a custom serialiser to write a BTreeMap
#[binrw::writer(writer, endian)]
fn custom_writer(
map: &BTreeMap<u16, u16>,
) -> BinResult<()> {
for (key, val) in map.iter() {
key.write_options(writer, endian, ())?;
val.write_options(writer, endian, ())?;
}
Ok(())
}
#[derive(BinWrite)]
#[bw(big)]
struct MyType {
#[bw(write_with = custom_writer)]
offsets: BTreeMap<u16, u16>
}
let object = MyType {
offsets: BTreeMap::from([(0, 1), (2, 3)]),
};
let mut output = Cursor::new(vec![]);
object.write(&mut output).unwrap();
assert_eq!(output.into_inner(), b"\0\0\0\x01\0\x02\0\x03");
§Using FilePtr::parse
to read a NullString
without storing a FilePtr
#[derive(BinRead)]
struct MyType {
#[br(parse_with = FilePtr32::parse)]
some_string: NullString,
}
§Debug
The dbg
directive prints the offset and value of a field to
stderr
for quick and dirty debugging:
#[br(dbg)]
The type of the field being inspected must implement Debug
.
Non-nightly Rust versions without support for
proc_macro_span
will emit
line numbers pointing to the binrw attribute on the parent struct or enum rather
than the field.
§Examples
#[derive(BinRead, Debug)]
#[br(little)]
struct Inner {
#[br(dbg)]
a: u32,
#[br(dbg)]
b: u32,
}
#[derive(BinRead, Debug)]
#[br(little)]
struct Test {
first: u16,
#[br(dbg)]
inner: Inner,
}
// prints:
//
// [file.rs:5 | offset 0x2] a = 0x10
// [file.rs:7 | offset 0x6] b = 0x40302010
// [file.rs:15 | offset 0x2] inner = Inner {
// a: 0x10,
// b: 0x40302010,
// }
Test::read(&mut Cursor::new(b"\x01\0\x10\0\0\0\x10\x20\x30\x40")).unwrap(),
§Enum errors
The return_all_errors
(default) and return_unexpected_error
directives
define how to handle errors when parsing an enum:
#[br(return_all_errors)]
#[br(return_unexpected_error)]
return_all_errors
collects the errors that occur when enum variants fail
to parse and returns them in binrw::Error::EnumErrors
when no variants
parse successfully:
#[derive(BinRead)]
#[br(return_all_errors)]
enum Test {
#[br(magic(0u8))]
A { a: u8 },
B { b: u32 },
C { #[br(assert(c != 1))] c: u8 },
}
let error = Test::read_le(&mut Cursor::new(b"\x01")).unwrap_err();
if let binrw::Error::EnumErrors { pos, variant_errors } = error {
assert_eq!(pos, 0);
assert!(matches!(variant_errors[0], ("A", binrw::Error::BadMagic { .. })));
assert!(matches!(
(variant_errors[1].0, variant_errors[1].1.root_cause()),
("B", binrw::Error::Io(..))
));
assert!(matches!(variant_errors[2], ("C", binrw::Error::AssertFail { .. })));
}
return_unexpected_error
discards the errors and instead returns a generic
binrw::Error::NoVariantMatch
if all variants fail to parse. This avoids
extra memory allocations required to collect errors, but only provides the
position when parsing fails:
#[derive(BinRead)]
#[br(return_unexpected_error)]
enum Test {
#[br(magic(0u8))]
A { a: u8 },
B { b: u32 },
C { #[br(assert(c != 1))] c: u8 },
}
let error = Test::read_le(&mut Cursor::new(b"\x01")).unwrap_err();
if let binrw::Error::NoVariantMatch { pos } = error {
assert_eq!(pos, 0);
}
§Ignore
For BinRead
, the ignore
directive, and its alias
default
, sets the value of the field to its
Default
instead of reading data from the reader:
#[br(default)] or #[br(ignore)]
For BinWrite
, the ignore
directive skips writing the
field to the writer:
#[bw(ignore)]
§Examples
#[derive(BinRead)]
struct Test {
#[br(ignore)]
path: Option<std::path::PathBuf>,
}
assert_eq!(
Test::read_le(&mut Cursor::new(b"")).unwrap(),
Test { path: None }
);
#[derive(BinWrite)]
struct Test {
a: u8,
#[bw(ignore)]
b: u8,
c: u8,
}
let object = Test { a: 1, b: 2, c: 3 };
let mut output = Cursor::new(vec![]);
object.write_le(&mut output).unwrap();
assert_eq!(
output.into_inner(),
b"\x01\x03"
);
§Magic
The magic
directive matches magic numbers
in data:
#[br(magic = $magic:literal)] or #[br(magic($magic:literal))]
#[bw(magic = $magic:literal)] or #[bw(magic($magic:literal))]
The magic number can be a byte literal, byte string, float, or integer. When a magic number is matched, parsing begins with the first byte after the magic number in the data. When a magic number is not matched, an error is returned.
§Examples
§Using byte strings
#[derive(BinRead)]
#[br(magic = b"TEST")]
struct Test {
val: u32
}
Test::read_le(&mut Cursor::new(b"TEST\0\0\0\0"))
#[derive(BinWrite)]
#[bw(magic = b"TEST")]
struct Test {
val: u32
}
let object = Test { val: 0 };
let mut output = Cursor::new(vec![]);
object.write_le(&mut output)
§Using float literals
#[derive(BinRead)]
#[br(big, magic = 1.2f32)]
struct Version(u16);
Version::read(&mut Cursor::new(b"\x3f\x99\x99\x9a\0\0"))
#[derive(BinWrite)]
#[bw(big, magic = 1.2f32)]
struct Version(u16);
let object = Version(0);
let mut output = Cursor::new(vec![]);
object.write(&mut output)
§Enum variant selection using magic
#[derive(BinRead)]
enum Command {
#[br(magic = 0u8)] Nop,
#[br(magic = 1u8)] Jump { loc: u32 },
#[br(magic = 2u8)] Begin { var_count: u16, local_count: u16 }
}
Command::read_le(&mut Cursor::new(b"\x01\0\0\0\0"))
#[derive(BinWrite)]
enum Command {
#[bw(magic = 0u8)] Nop,
#[bw(magic = 1u8)] Jump { loc: u32 },
#[bw(magic = 2u8)] Begin { var_count: u16, local_count: u16 }
}
let object = Command::Jump { loc: 0 };
let mut output = Cursor::new(vec![]);
object.write_le(&mut output)
§Errors
If the specified magic number does not match the data, a
BadMagic
error is returned and the reader’s
position is reset to where it was before parsing started.
§Map
The map
and try_map
directives allow data to be read using one type and
stored as another:
#[br(map = $map_fn:expr)] or #[br(map($map_fn:expr)))]
#[br(try_map = $map_fn:expr)] or #[br(try_map($map_fn:expr)))]
#[bw(map = $map_fn:expr)] or #[bw(map($map_fn:expr)))]
#[bw(try_map = $map_fn:expr)] or #[bw(try_map($map_fn:expr)))]
When using #[br(map)]
on a field, the map function must
explicitly declare the type of the data to be read in its first parameter
and return a value which matches the type of the field.
When using #[bw(map)]
on a field, the map function will receive
an immutable reference to the field value and must return a type which
implements BinWrite
.
When using map
on a field, the map function must
explicitly declare the type of the data to be read in its first parameter
and return a value which matches the type of the field.
When using map
on a field, the map function will receive
an immutable reference to the field value and must return a type which
implements BinWrite
.
The map function can be a plain function, closure, or call expression which
returns a plain function or closure.
When using try_map
, the same rules apply, except that the function must
return a Result<T, E>
instead.
When using map
or try_map
on a struct or enum, the map function
must return Self
(map
) or Result<Self, E>
(try_map
)
for BinRead
. For BinWrite
, it will receive an immutable reference to the
entire object and must return a type that implements BinWrite
.
must return Self
(map
) or Result<Self, E>
(try_map
).
will receive an immutable reference to the entire object
and must return a type that implements BinWrite
.
Any (earlier only, when reading)earlier field or import can be referenced by the expression in the directive.
§Examples
§Using map
on a field
#[derive(BinRead)]
struct MyType {
#[br(map = |x: u8| x.to_string())]
int_str: String
}
#[derive(BinWrite)]
struct MyType {
#[bw(map = |x| x.parse::<u8>().unwrap())]
int_str: String
}
let object = MyType { int_str: String::from("1") };
let mut output = Cursor::new(vec![]);
object.write_le(&mut output).unwrap();
assert_eq!(output.into_inner(), b"\x01");
§Using try_map
on a field
#[derive(BinRead)]
struct MyType {
#[br(try_map = |x: i8| x.try_into())]
value: u8
}
#[derive(BinWrite)]
struct MyType {
#[bw(try_map = |x| { i8::try_from(*x) })]
value: u8
}
let mut writer = Cursor::new(Vec::new());
writer.write_be(&MyType { value: 3 });
assert_eq!(writer.into_inner(), b"\x03")
§Using map
on a struct to create a bit field
The modular-bitfield
crate can be used
along with map
to create a struct out of raw bits.
use modular_bitfield::prelude::*;
// This reads a single byte from the reader
#[bitfield]
#[derive(BinRead)]
#[br(map = Self::from_bytes)]
pub struct PackedData {
status: B4,
is_fast: bool,
is_static: bool,
is_alive: bool,
is_good: bool,
}
// example byte: 0x53
// [good] [alive] [static] [fast] [status]
// 0 1 0 1 0011
// false true false true 3
let data = Cursor::new(b"\x53").read_le::<PackedData>().unwrap();
assert_eq!(data.is_good(), false);
assert_eq!(data.is_alive(), true);
assert_eq!(data.is_static(), false);
assert_eq!(data.is_fast(), true);
assert_eq!(data.status(), 3);
use modular_bitfield::prelude::*;
// The cursor dumps a single byte
#[bitfield]
#[derive(BinWrite, Clone, Copy)]
#[bw(map = |&x| Self::into_bytes(x))]
pub struct PackedData {
status: B4,
is_fast: bool,
is_static: bool,
is_alive: bool,
is_good: bool,
}
let object = PackedData::new()
.with_is_alive(true)
.with_is_fast(true)
.with_status(3);
let mut output = Cursor::new(vec![]);
output.write_le(&object).unwrap();
assert_eq!(output.into_inner(), b"\x53");
§Errors
If the try_map
function returns a binrw::io::Error
or std::io::Error
, an Io
error is returned. For
any other error type, a Custom
error is returned.
In all cases, the reader’swriter’s position is reset to where it was before parsing started.
§Offset
The offset
directive is shorthand for passing offset
to a parser that
operates like FilePtr
:
#[br(offset = $offset:expr)] or #[br(offset($offset:expr))]
When manually implementing
BinRead::read_options
or a
custom parser function, the offset is accessible
from a named argument named offset
.
Any (earlier only, when reading)earlier field or import can be referenced by the expression in the directive.
§Examples
#[derive(BinRead)]
#[br(little)]
struct OffsetTest {
#[br(offset = 4)]
test: FilePtr<u8, u16>
}
§Errors
If seeking to or reading from the offset fails, an Io
error is returned and the reader’s position is reset to where it was before
parsing started.
§Padding and alignment
binrw includes directives for common forms of data structure alignment.
The pad_before
and pad_after
directives skip a specific number of bytes
either before or after
readingwriting a field,
respectively:
#[br(pad_after = $skip_bytes:expr)] or #[br(pad_after($skip_bytes:expr))]
#[br(pad_before = $skip_bytes:expr)] or #[br(pad_before($skip_bytes:expr))]
#[bw(pad_after = $skip_bytes:expr)] or #[bw(pad_after($skip_bytes:expr))]
#[bw(pad_before = $skip_bytes:expr)] or #[bw(pad_before($skip_bytes:expr))]
This is equivalent to:
pos += padding;
The align_before
and align_after
directives align the next
readwrite to the
given byte alignment either before or after
readingwriting a field,
respectively:
#[br(align_after = $align_to:expr)] or #[br(align_after($align_to:expr))]
#[br(align_before = $align_to:expr)] or #[br(align_before($align_to:expr))]
#[bw(align_after = $align_to:expr)] or #[bw(align_after($align_to:expr))]
#[bw(align_before = $align_to:expr)] or #[bw(align_before($align_to:expr))]
This is equivalent to:
if pos % align != 0 {
pos += align - (pos % align);
}
The seek_before
directive accepts a SeekFrom
object and seeks the
readerwriter to an arbitrary
position before
readingwriting a field:
#[br(seek_before = $seek_from:expr)] or #[br(seek_before($seek_from:expr))]
#[bw(seek_before = $seek_from:expr)] or #[bw(seek_before($seek_from:expr))]
This is equivalent to:
stream.seek(seek_from)?;
The position of the
readerwriter will not be
restored after the seek; use the
restore_position
directive to seek, then
readwrite, then restore
position.
The pad_size_to
directive will ensure that the
readerwriter has advanced at
least the number of bytes given after the field has been
readwritten:
#[br(pad_size_to = $size:expr)] or #[br(pad_size_to($size:expr))]
#[bw(pad_size_to = $size:expr)] or #[bw(pad_size_to($size:expr))]
For example, if a format uses a null-terminated string, but always reserves
at least 256 bytes for that string, NullString
will
read the string and pad_size_to(256)
will ensure the reader skips whatever
padding, if any, remains. If the string is longer than 256 bytes, no padding
will be skipped.
Any (earlier only, when reading)earlier field or import can be referenced by the expressions in any of these directives.
§Examples
#[derive(BinRead)]
struct MyType {
#[br(align_before = 4, pad_after = 1, align_after = 4)]
str: NullString,
#[br(pad_size_to = 0x10)]
test: u64,
#[br(seek_before = SeekFrom::End(-4))]
end: u32,
}
#[derive(BinWrite)]
struct MyType {
#[bw(align_before = 4, pad_after = 1, align_after = 4)]
str: NullString,
#[bw(pad_size_to = 0x10)]
test: u64,
#[bw(seek_before = SeekFrom::End(-4))]
end: u32,
}
§Errors
If seeking fails, an Io
error is returned and the
reader’swriter’s position is
reset to where it was before
parsingserialisation
started.
§Pre-assert
pre_assert
works like assert
, but checks the condition before
data is read instead of after:
#[br(pre_assert($cond:expr $(,)?))]
#[br(pre_assert($cond:expr, $msg:literal $(,)?)]
#[br(pre_assert($cond:expr, $fmt:literal, $($arg:expr),* $(,)?))]
#[br(pre_assert($cond:expr, $err:expr $(,)?)]
This is most useful when validating arguments or selecting an enum variant.
§Examples
#[derive(BinRead)]
#[br(import { ty: u8 })]
enum Command {
#[br(pre_assert(ty == 0))] Variant0(u16, u16),
#[br(pre_assert(ty == 1))] Variant1(u32)
}
#[derive(BinRead)]
struct Message {
ty: u8,
len: u8,
#[br(args { ty })]
data: Command
}
let msg = Cursor::new(b"\x01\x04\0\0\0\xFF").read_be::<Message>();
assert!(msg.is_ok());
let msg = msg.unwrap();
assert_eq!(msg, Message { ty: 1, len: 4, data: Command::Variant1(0xFF) });
§Repr
The repr
directive is used on a unit-like (C-style) enum to specify the
underlying type to use when
readingwriting the
field and matching variants:
#[br(repr = $ty:ty)] or #[br(repr($ty:ty))]
#[bw(repr = $ty:ty)] or #[bw(repr($ty:ty))]
§Examples
#[derive(BinRead)]
#[br(big, repr = i16)]
enum FileKind {
Unknown = -1,
Text,
Archive,
Document,
Picture,
}
#[derive(BinWrite)]
#[bw(big, repr = i16)]
enum FileKind {
Unknown = -1,
Text,
Archive,
Document,
Picture,
}
§Errors
If a readwrite fails, an
Io
error is returned. If no variant
matches, a NoVariantMatch
error is
returned.
In all cases, the reader’swriter’s position is reset to where it was before parsingserialisation started.
§Restore position
The restore_position
directive restores the position of the
readerwriter after a field
is readwritten:
#[br(restore_position)]
#[bw(restore_position)]
To seek to an arbitrary position, use seek_before
instead.
§Examples
#[derive(BinRead)]
struct MyType {
#[br(restore_position)]
test: u32,
test_bytes: [u8; 4]
}
#[derive(BinWrite)]
#[bw(big)]
struct Relocation {
#[bw(ignore)]
delta: u32,
#[bw(seek_before(SeekFrom::Current((*delta).into())))]
reloc: u32,
}
#[derive(BinWrite)]
#[bw(big)]
struct Executable {
#[bw(restore_position)]
code: Vec<u8>,
relocations: Vec<Relocation>,
}
let object = Executable {
code: vec![ 1, 2, 3, 4, 0, 0, 0, 0, 9, 10, 0, 0, 0, 0 ],
relocations: vec![
Relocation { delta: 4, reloc: 84281096 },
Relocation { delta: 2, reloc: 185339150 },
]
};
let mut output = Cursor::new(vec![]);
object.write(&mut output).unwrap();
assert_eq!(
output.into_inner(),
b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e"
);
§Errors
If querying or restoring the
readerwriter position fails,
an Io
error is returned and the
reader’swriter’s
position is reset to where it was before
parsingserialisation
started.
§Stream access and manipulation
The stream
directive allows direct access to the underlying
readwrite stream on a struct or
enum:
#[br(stream = $ident:ident)] or #[br(stream($ident:ident))]
#[bw(stream = $ident:ident)] or #[bw(stream($ident:ident))]
The map_stream
directive allows the readwrite
stream to be replaced with another stream when readingwriting
an object or field:
#[br(map_stream = $map_fn:expr)] or #[br(map_stream($map_fn:expr))]
#[bw(map_stream = $map_fn:expr)] or #[bw(map_stream($map_fn:expr))]
The map function can be a plain function, closure, or call expression which
returns a plain function or closure. The returned object must
implement Read
+ Seek
(for reading)
or Write
+ Seek
(for writing).
The mapped stream is used to readwrite the contents of the field or object it is applied to:
fn make_stream<S: binrw::io::Read + binrw::io::Seek>(s: S) -> /* … */
/* … */
}
#[derive(BinRead)]
// `magic` does not use the mapped stream
#[br(magic = b"foo", map_stream = make_stream)]
struct Foo {
// everything inside the struct uses the mapped stream
a: u32,
/* … */
}
#[derive(BinRead)]
struct Bar {
// `pad_before` and `pad_after` do not use the mapped stream
#[br(pad_before(4), pad_after(4), map_stream = make_stream)]
b: u64 // reading `u64` uses the mapped stream
}
fn make_stream<S: binrw::io::Write + binrw::io::Seek>(s: S) -> /* … */
/* … */
}
#[derive(BinWrite)]
// `magic` does not use the mapped stream
#[bw(magic = b"foo", map_stream = make_stream)]
struct Foo {
// everything inside the struct uses the mapped stream
a: u32,
/* … */
}
#[derive(BinWrite)]
struct Bar {
// `pad_before` and `pad_after` do not use the mapped stream
#[bw(pad_before(4), pad_after(4), map_stream = make_stream)]
b: u64 // writing `u64` uses the mapped stream
}
§Examples
§Verifying a checksum
#[binread]
#[br(little, stream = r, map_stream = Checksum::new)]
struct Test {
a: u16,
b: u16,
#[br(temp, assert(c == r.check() - c, "bad checksum: {:#x?} != {:#x?}", c, r.check() - c))]
c: u8,
}
assert_eq!(
Test::read(&mut Cursor::new(b"\x01\x02\x03\x04\x0a")).unwrap(),
Test {
a: 0x201,
b: 0x403,
}
);
§Reading encrypted blocks
#[binread]
#[br(little)]
struct Test {
iv: u8,
#[br(parse_with = until_eof, map_stream = |reader| BadCrypt::new(reader, iv))]
data: Vec<u8>,
}
assert_eq!(
Test::read(&mut Cursor::new(b"\x01\x03\0\x04\x01")).unwrap(),
Test {
iv: 1,
data: vec![2, 3, 4, 5],
}
);
§Writing a checksum
#[binwrite]
#[bw(little, stream = w, map_stream = Checksum::new)]
struct Test {
a: u16,
b: u16,
#[bw(calc(w.check()))]
c: u8,
}
let mut out = Cursor::new(Vec::new());
Test { a: 0x201, b: 0x403 }.write(&mut out).unwrap();
assert_eq!(out.into_inner(), b"\x01\x02\x03\x04\x0a");
§Writing encrypted blocks
#[binwrite]
#[bw(little)]
struct Test {
iv: u8,
#[bw(map_stream = |writer| BadCrypt::new(writer, *iv))]
data: Vec<u8>,
}
let mut out = Cursor::new(Vec::new());
Test { iv: 1, data: vec![2, 3, 4, 5] }.write(&mut out).unwrap();
assert_eq!(out.into_inner(), b"\x01\x03\0\x04\x01");
§Temp
This directive can only be used with binread
. It
will not work with #[derive(BinRead)]
.
When using #[br(temp)]
, the #[binread]
attribute must be placed before
other attributes like #[derive(Debug)]
. Otherwise, the other attributes will
generate code that references non-existent fields, and compilation will fail.
The temp
directive causes a field to be treated as a temporary variable
instead of an actual field. The field will be removed from the struct
definition generated by binread
:
#[br(temp)]
This allows data to be read which is necessary for parsing an object but which doesn’t need to be stored in the final object. To skip data entirely, use an alignment directive instead.
§Examples
#[binread] // ← must be before other attributes that use struct fields
#[br(big)]
struct Test {
// Since `Vec` stores its own length, this field is redundant
#[br(temp)]
len: u32,
#[br(count = len)]
data: Vec<u8>
}
assert_eq!(
Test::read(&mut Cursor::new(b"\0\0\0\x05ABCDE")).unwrap(),
Test { data: b"ABCDE".to_vec() }
);
§Try
The try
directive allows parsing of a field to fail instead
of returning an error:
#[br(try)]
If the field cannot be parsed, the position of the reader will be restored
and the value of the field will be set to the default
value for the type.
§Examples
#[derive(BinRead)]
struct MyType {
#[br(try)]
maybe_u32: Option<u32>
}
assert_eq!(Cursor::new(b"").read_be::<MyType>().unwrap().maybe_u32, None);