Module binread::attribute[][src]

A documentation-only module for the possible directives used in #[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_tuplestruct 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.
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 derive_binread attribute macro.
tryfieldReads data into an Option, but stores None 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::default();
options.endian = 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, binread::Error::AssertFail { message: expected, .. }));

Custom error

#[derive(Debug, PartialEq)]
struct NotSmallerError(u32, u32);

#[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, respectively:

#[br(import($($ident:ident : $ty:ty),* $(,)?))]
#[br(args($($ident:ident),* $(,)?))]

Any earlier field or import can be referenced in args.

Examples

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

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

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 derive_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 derive_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

#[derive_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 an Option 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 None.

Examples

#[derive(BinRead)]
struct MyType {
    #[br(try)]
    maybe_u32: Option<u32>
}

assert_eq!(Cursor::new(b"").read_be::<MyType>().unwrap().maybe_u32, None);

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

When manually implementing BinRead::read_options or a custom parser function, the count value is accessible from ReadOptions::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(count = size)]
    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))]

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

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

The position of the reader will not be restored after the seek; use the restore_position directive for this.

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.