Module binrw::attribute::read[][src]

Expand description

A documentation-only module for directives used in the #[br] and #[binread] attributes.

List of directives

DirectiveSupportsDescription
align_afterfieldAligns the reader to the Nth byte after reading data.
align_beforefieldAligns the reader to the Nth byte before reading data.
argsstruct field, data variantPasses arguments to another BinRead object.
args_rawstruct field, data variantLike args, but specifies a tuple containing the arguments.
assertstruct, field, non-unit enum, data variantAsserts that a condition is true. Can be used multiple times.
bigall except unit variantSets the byte order to big-endian.
calcfieldComputes the value of a field instead of reading data.
countfieldSets the length of a vector.
defaultfieldUses the default value for a field instead of reading data.
deref_nowfieldAn alias for postprocess_now.
err_contextfieldAdd additional context to errors.
iffieldReads data only if a condition is true.
ignorefieldAn alias for default.
importstruct, non-unit enum, unit-like enumDefines extra arguments for a struct or enum.
import_tuplestruct, non-unit enum, unit-like enumLike import, but receives the arguments as a tuple.
is_bigfieldConditionally sets the byte order to big-endian.
is_littlefieldConditionally set the byte order to little-endian.
littleall except unit variantSets the byte order to little-endian.
magicallMatches a magic number.
mapall except unit variantMaps a read value to a new value. When used on a struct or enum, the map function must return Self.
offsetfieldModifies the offset used by a FilePtr.
pad_afterfieldSkips N bytes after reading a field.
pad_beforefieldSkips N bytes before reading a field.
pad_size_tofieldEnsures the reader is at least N bytes after the starting position for this field.
parse_withfieldSpecifies a custom function for reading a field.
postprocess_nowfieldCalls after_parse immediately after reading data instead of after all fields have been read.
pre_assertstruct, non-unit enum, unit variantLike assert, but checks the condition before parsing.
reprunit-like enumSpecifies the underlying type for a unit-like (C-style) enum.
restore_positionfieldRestores the reader’s position after reading a field.
return_all_errorsnon-unit enumReturns a Vec containing the error which occurred on each variant of an enum on failure. This is the default.
return_unexpected_errornon-unit enumReturns a single generic error on failure.
seek_beforefieldMoves the reader to a specific position before reading data.
tempfieldUses a field as a temporary variable. Only usable with the binread attribute macro.
tryfieldTries to parse and stores the default value for the type if parsing fails instead of returning an error.
try_mapall except unit variantLike map, but returns a BinResult.

Byte order

The big and little directives specify the byte order of data in a struct, enum, variant, or field:

#[br(big)]
#[br(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))]

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:

  1. A directive on a field
  2. A directive on an enum variant
  3. A directive on the struct or enum
  4. The endian property of the ReadOptions object passed to BinRead::read_options by the caller
  5. The host machine’s native byte order

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, Debug, PartialEq)]
#[br(little)] // ← this *forces* the struct to be little-endian
struct Child(u32);

#[derive(BinRead, Debug)]
struct Parent {
    #[br(big)] // ← this will be ignored
    child: Child,
};

let mut options = ReadOptions::new(Endian::Big /* ← this will be ignored */);
Child::read_options(&mut Cursor::new(b"\x01\0\0\0"), &options, ())

When manually implementing BinRead::read_options or a custom parser function, the byte order is accessible from ReadOptions::endian.

Examples

#[derive(BinRead)]
#[br(little)]
struct MyType (
    #[br(big)] u32, // ← will be big-endian
    u32, // ← will be little-endian
);
#[derive(BinRead, Debug, PartialEq)]
#[br(big)]
struct MyType {
    val: u8,
    #[br(is_little = (val == 3))]
    other_val: u16 // ← little-endian if `val == 3`, otherwise big-endian
}

Magic

The magic directive matches magic numbers in data:

#[br(magic = $magic:literal)] or #[br(magic($magic:literal))]

The magic number can be a byte literal, byte string, char, 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

#[derive(BinRead, Debug)]
#[br(magic = b"TEST")]
struct Test {
    val: u32
}

#[derive(BinRead, Debug)]
#[br(magic = 1.2f32)]
struct Version(u16);

#[derive(BinRead)]
enum Command {
    #[br(magic = 0u8)] Nop,
    #[br(magic = 1u8)] Jump { loc: u32 },
    #[br(magic = 2u8)] Begin { var_count: u16, local_count: u16 }
}

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.

Assert

The assert directive validates objects and fields after they are read, 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 $(,)?)]

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(Debug, PartialEq)]
struct NotSmallerError(u32, u32);

#[derive(BinRead, Debug)]
#[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, .. }));

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

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 reader’s position is reset to where it was before parsing started.

Pre-assert

pre_assert works like assert, but checks the condition before data is read instead of after. This is most useful when validating arguments or choosing an enum variant to parse.

#[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 $(,)?)]

Examples

#[derive(BinRead, Debug, PartialEq)]
#[br(import { ty: u8 })]
enum Command {
    #[br(pre_assert(ty == 0))] Variant0(u16, u16),
    #[br(pre_assert(ty == 1))] Variant1(u32)
}

#[derive(BinRead, Debug, PartialEq)]
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) });

Arguments

The import and args directives define the type of BinRead::Args and the values passed in the args argument of a BinRead::read_options call.

Any earlier field or import can be referenced in args.

There are 3 types of arguments:

  • Tuple-styled Arguments (Alternatively “Ordered Arguments”) - arguments passed as a tuple
  • Named arguments - arguments passed as a builder that ensures all required arguments are passed (can be manually constructed using binrw::args)
  • Raw arguments - the arguments are passed as a type of your choice

Examples

Tuple-styled arguments

Tuple-styled arguments are passed via args() and recieved via import().

#[derive(BinRead)]
#[br(import(val1: u32, val2: &'static str))]
struct ImportTest {
    // ...
}

#[derive(BinRead)]
struct ArgsTets {
    val: u32,
    #[br(args(val + 3, "test"))]
    test: ImportTest
}

Named arguments

Named arguments are passed via args {} and recieved via import {}. (Note the curly braces)

#[derive(BinRead)]
#[br(import { count: u32, other: u16 = 0 })]
struct ImportTest {
    // ...
}

#[derive(BinRead)]
struct ArgsTets {
    count: u32,

    #[br(args { count, other: 5 })]
    test: ImportTest,

    #[br(args { count: 3 })]
    test2: ImportTest,
}

The syntax is designed to mimic Rust’s struct literal syntax. Similarly, if you have a previously defined variable of the same name as an argument to be passed in, you can just provide the name of the variable and omit the : $value portion of passing the argument.

Another feature of named imports is allowing to specify a default value in the form of name: type = value, which makes passing the argument optional.

Raw arguments

Raw arguments can be used to have a higher degree of control over the type of the arguments variable being passed into the parser.


#[derive(BinRead)]
#[br(import_raw(args: (u32, u16)))]
struct ImportTest {
    // ...
}

#[derive(BinRead)]
struct ArgsTets {
    count: u32,

    #[br(args(1, 2))]
    test: ImportTest,

    // identical to the above
    #[br(args_raw = (1, 2))]
    test2: ImportTest,
}

One common use of import_raw and args_raw is for easily forwarding arguments through to an inner field of the structure.

Technical notes

The format for the import and args directives are as follows:

// tuple-styled args
#[br(import($($ident:ident : $ty:ty),* $(,)?))]
#[br(args($($value:expr),* $(,)?))]

// named args
#[br(import{ $($ident:ident : $ty:ty $(= $default:expr)? ),* $(,)? })]
#[br(args { $($name:ident $(: $value:expr)? ),* $(,)? } )]

// raw args
#[br(import_raw( $binding:ident : $ty:ty ))]
#[br(args_raw($value:expr))]
#[br(args_raw = $value:expr)] // same as above, alternative syntax

A notable limitation of the arguments system is not allowing non-static lifetimes. This is due to the fact arguments desugar into approximately the following:

impl BinRead for MyType {
    type Args = $ty;

    fn read_options(..., args: Self::Args) -> Result<Self, binrw::Error> {
        // ...
    }
}

Which, due to the fact the associated type Args cannot have a lifetime tied to the associated function read_options, the type is inexpressible without GATs.

Default

The default directive, and its alias ignore, sets the value of the field to its Default instead of reading data from the reader:

#[br(default)] or #[br(ignore)]

Examples

#[derive(BinRead, Debug, PartialEq)]
struct Test {
    #[br(default)]
    path: Option<std::path::PathBuf>,
}

assert_eq!(
    Test::read(&mut Cursor::new(b"")).unwrap(),
    Test { path: None }
);

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]
#[derive(Debug, PartialEq)]
struct Test {
    // Since `Vec` stores its own length, this field is redundant
    #[br(temp, big)]
    len: u32,

    #[br(count = len)]
    data: Vec<u8>
}

assert_eq!(
    Test::read(&mut Cursor::new(b"\0\0\0\x05ABCDE")).unwrap(),
    Test { data: Vec::from(&b"ABCDE"[..]) }
);

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,
}

Restore position

The restore_position directive restores the position of the reader after a field is read:

#[br(restore_position)]

To seek to an arbitrary position, use seek_before instead.

Examples

#[derive(BinRead, Debug, PartialEq)]
struct MyType {
    #[br(restore_position)]
    test: u32,
    test_bytes: [u8; 4]
}

Errors

If querying or restoring the reader position fails, an Io error is returned and the reader’s position is reset to where it was before parsing started.

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

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,
}

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 #[map($map_fn:expr))]
#[br(try_map = $map_fn:expr)] or #[try_map($map_fn:expr))]

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. The map function can be a plain function, closure, or call expression which returns a plain function or closure.

When using try_map on a field, the same rules apply, except that the function must return a Result instead.

When using map or try_map on a struct or enum, the map function must return Self or Result<Self, E>.

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
}

Using try_map on a field

#[derive(BinRead)]
struct MyType {
    #[br(try_map = |x: i8| x.try_into())]
    value: u8
}

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

Errors

If the try_map function returns a binread::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’s position is reset to where it was before parsing started.

Custom parsers

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

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

Examples

Using a custom parser to generate a HashMap

fn custom_parser<R: Read + Seek>(reader: &mut R, ro: &ReadOptions, _: ())
    -> BinResult<HashMap<u16, u16>>
{
    let mut map = HashMap::new();
    map.insert(
        reader.read_be().unwrap(),
        reader.read_be().unwrap()
    );
    Ok(map)
}

#[derive(BinRead)]
struct MyType {
    #[br(parse_with = custom_parser)]
    offsets: HashMap<u16, u16>
}

Using FilePtr::parse to read a NullString without storing a FilePtr

#[derive(BinRead)]
struct MyType {
    #[br(parse_with = FilePtr32::parse)]
    some_string: NullString,
}

Calculations

The calc directive computes the value of a field instead of reading data from the reader:

#[br(calc = $value:expr)] or #[br(calc($value:expr))]

Any earlier field or import can be referenced by the expression in the directive.

Examples

#[derive(BinRead)]
struct MyType {
    var: u32,
    #[br(calc = 3 + var)]
    var_plus_3: u32,
}

Count

The count directive sets the number of values to read into a repeating collection type like a Vec:

#[br(count = $count:expr) or #[br(count($count:expr))]

This is effectively equivelant to writing:

#[br(args { count: $count as usize })]

As such, 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 MyType {
    size: u32,
    #[br(count = size)]
    data: Vec<u8>,
}

Using count with FilePtr and Vec

#[derive(BinRead)]
struct MyType {
    size: u32,
    #[br(parse_with = FilePtr::with(count(size as usize)))]
    data: FilePtr<u32, Vec<u8>>,
}

Offset

The offset and offset_after directives specify an additional relative offset to a value accessed by a BinRead implementation which reads data from an offset, 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 ReadOptions::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, Debug, PartialEq)]
struct OffsetTest {
    #[br(little, 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.

Conditional values

The if directive allows conditional parsing of a field, reading from data if the condition is true and using a computed value if the condition is false:

#[br(if = $cond:expr)] or #[br(if($cond:expr))]
#[br(if = $cond:expr, $alternate:expr)] or #[br(if($cond:expr, $alternate:expr))]

If an alternate is provided, that value will be used when the condition is false; otherwise, the default value for the type will be used.

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.

Examples

Using 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>,
}

Using 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,
}

Padding and alignment

BinRead offers different 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 reading 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))]

This is effectively equivelant to:

pos += padding;

The align_before and align_after directives align the next read to the given byte alignment either before or after reading 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))]

This is effectively equivelant to:

if pos % align != 0 {
   pos += align - (pos % align);
}

The seek_before directive accepts a SeekFrom object and seeks the reader to an arbitrary position before reading a field:

#[br(seek_before = $seek_from:expr)] or #[br(seek_before($seek_from:expr))]

This is equivelant to:

reader.seek($seek_from)?;

The position of the reader will not be restored after the seek; use the restore_position directive if you wish to seek -> read -> restore position.


The pad_size_to directive will ensure that the reader has advanced at least the number of bytes given after the field has been read:

#[br(pad_size_to = $size:expr)] or #[br(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,
}

Errors

If seeking fails, an Io error is returned and the reader’s position is reset to where it was before parsing started.

Repr

The repr directive is used on a unit-like (C-style) enum to specify the underlying type to use when reading the field and matching variants:

#[br(repr = $ty:ty)] or #[br(repr($ty:ty))]

Examples

#[derive(BinRead)]
#[br(big, repr = i16)]
enum FileKind {
    Unknown = -1,
    Text,
    Archive,
    Document,
    Picture,
}

Errors

If a read fails, an Io error is returned. If no variant matches, a NoVariantMatch error is returned.

In all cases, the reader’s position is reset to where it was before parsing started.