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 | struct field, data variant | Passes arguments to another binrw object. |
rw | args_raw | struct field, data variant | 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 | deref_now | field | An alias for postprocess_now . |
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. |
r | offset_after | field | Modifies the offset used by a FilePtr after 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 | postprocess_now | field | Calls after_parse immediately after reading data instead of after all fields have been read. |
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. |
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 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, using a builder that
ensures all required arguments are given, or manually constructed using
binrw::args
- 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: &'static str))]
struct Child {
// ...
}
#[derive(BinRead)]
struct Parent {
val: u32,
#[br(args(val + 3, "test"))]
test: Child
}
#[derive(BinWrite)]
#[bw(import(val1: u32, val2: &'static 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,
}
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,
}
Limitations
Borrowing values
Non-static lifetimes for borrowed values are currently unavailable because the associated type would require GATs to properly bind lifetimes to the function.
Named arguments conflicting with count
directive
The count
directive may conflict with args
. To pass arguments
to a type inside a Vec
, manually specify the count via named arguments
instead of using the count
directive. See VecArgs
for
details.
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 field or import can be referenced by expressions in the directive.
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)));
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 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)]
.
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 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]
#[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]
#[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 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
or Result<Self, E>
for BinRead
, and
will receive an immutable reference to the entire object
and must return a type that implements BinWrite
for
BinWrite
.
must return Self
or Result<Self, E>
.
will receive an immutable reference to the entire object
and must return a type that implements BinWrite
.
Any 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
and offset_after
directives are shorthands for passing
offset
and offset_after
arguments to a parser that operates like
FilePtr
:
#[br(offset = $offset:expr)] or #[br(offset($offset:expr))]
#[br(offset_after = $offset:expr)] or #[br(offset_after($offset:expr))]
When manually implementing
BinRead::read_options
or a
custom parser function, the offset is accessible
from a named argument named offset
.
For offset
, any earlier field or import can be referenced by
the expression in the directive.
For offset_after
, all fields and imports can be referenced by the
expression in the directive, but deref_now
cannot be
used.
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 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.
Postprocessing
The deref_now
directive, and its alias postprocess_now
, cause a
field’s after_parse
function to be called
immediately after the field is parsed, instead of deferring the call until
the entire parent object has been parsed:
#[br(deref_now)] or #[br(postprocess_now)]
The BinRead::after_parse
function is
normally used to perform additional work after the whole parent object has
been parsed. For example, the FilePtr
type reads an
object offset during parsing with
read_options
, then actually seeks to and
parses the pointed-to object in after_parse
. This improves read
performance by reading the whole parent object sequentially before seeking
to read the pointed-to object.
However, if another field in the parent object needs to access data from the
pointed-to object, after_parse
needs to be called earlier. Adding
deref_now
(or its alias, postprocess_now
) to the earlier field causes
this to happen.
Examples
#[derive(BinRead, Debug)]
#[br(big, magic = b"TEST")]
struct TestFile {
#[br(deref_now)]
ptr: FilePtr32<NullString>,
value: i32,
// Notice how `ptr` can be used as it has already been postprocessed
#[br(calc = ptr.len())]
ptr_len: usize,
}
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
.
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)]
.
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]
#[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);