Module binrw::docs::attribute

source ·
Expand description

Glossary of directives in binrw attributes (#[br], #[bw], #[brw]).

View for:

List of directives

r/wDirectiveSupports*Description
rwalign_afterfieldAligns the readerwriter to the Nth byte after a field.
rwalign_beforefieldAligns the readerwriter to the Nth byte before a field.
rwargsfieldPasses arguments to another binrw object.
rwargs_rawfieldLike args, but specifies a single variable containing the arguments.
rwassertstruct, field, non-unit enum, data variantAsserts that a condition is true. Can be used multiple times.
rwbigall except unit variantSets the byte order to big-endian.
rwcalcfieldComputes the value of a field instead of reading datausing a field.
rcountfieldSets the length of a vector.
rdbgfieldPrints the value and offset of a field to stderr.
rdefaultfieldAn alias for ignore.
rerr_contextfieldAdds additional context to errors.
rwiffieldReads or writesReadsWrites data only if a condition is true.
rwignorefieldFor 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.
rwimportstruct, non-unit enum, unit-like enumDefines extra arguments for a struct or enum.
rwimport_rawstruct, non-unit enum, unit-like enumLike import, but receives the arguments as a single variable.
rwis_bigfieldConditionally sets the byte order to big-endian.
rwis_littlefieldConditionally set the byte order to little-endian.
rwlittleall except unit variantSets the byte order to little-endian.
rwmagicallMatchesWrites a magic number.
rwmapall except unit variantMaps an object or value to a new value.
rwmap_streamall except unit variantMaps the readwrite stream to a new stream.
roffsetfieldModifies the offset used by a FilePtr while parsing.
rwpad_afterfieldSkips N bytes after readingwriting a field.
rwpad_beforefieldSkips N bytes before readingwriting a field.
rwpad_size_tofieldEnsures the readerwriter is always advanced at least N bytes.
rparse_withfieldSpecifies a custom function for reading a field.
rpre_assertstruct, non-unit enum, unit variantLike assert, but checks the condition before parsing.
rwreprunit-like enumSpecifies the underlying type for a unit-like (C-style) enum.
rwrestore_positionfieldRestores the reader’swriter’s position after readingwriting a field.
rreturn_all_errorsnon-unit enumReturns a Vec containing the error which occurred on each variant of an enum on failure. This is the default.
rreturn_unexpected_errornon-unit enumReturns a single generic error on failure.
rwseek_beforefieldMoves the readerwriter to a specific position before readingwriting data.
rwstreamstruct, non-unit enum, unit-like enumExposes the underlying readwrite stream.
rtempfieldUses a field as a temporary variable. Only usable with the binread attribute macro.
rtryfieldTries to parse and stores the default value for the type if parsing fails instead of returning an error.
rwtry_calcfieldLike calc, but returns a Result.
rwtry_mapall except unit variantLike map, but returns a Result.
wwrite_withfieldSpecifies 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 BinReadBinWrite 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:

  1. 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.

  2. Otherwise, only a single argument is allowed, which will then be attached as a context type. This type must implement Display, Debug, Send, and Sync.

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:

  1. A directive on a field
  2. A directive on an enum variant
  3. A directive on the struct or enum
  4. The endian parameter of the BinRead::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_optionsBinWrite::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 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]
#[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 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)].

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);