8 releases (4 breaking)

new 0.11.1 Nov 13, 2024
0.11.0 Apr 4, 2024
0.10.1 Jun 3, 2023
0.10.0 Jul 24, 2021
0.7.1 Feb 12, 2020

#99 in #byte-slice

Download history 105/week @ 2024-07-29 107/week @ 2024-08-05 107/week @ 2024-08-12 67/week @ 2024-08-19 97/week @ 2024-08-26 87/week @ 2024-09-02 101/week @ 2024-09-09 88/week @ 2024-09-16 149/week @ 2024-09-23 97/week @ 2024-09-30 32/week @ 2024-10-07 94/week @ 2024-10-14 89/week @ 2024-10-21 50/week @ 2024-10-28 88/week @ 2024-11-04 134/week @ 2024-11-11

366 downloads per month
Used in 6 crates (via bitbuffer)

MIT/Apache

52KB
1.5K SLoC

Automatically generate BitRead and BitReadSized implementations for structs and enums

Structs

The implementation can be derived for a struct as long as every field in the struct implements BitRead or BitReadSized

The struct is read field by field in the order they are defined in, if the size for a field is set stream.read_sized() will be used, otherwise stream_read() will be used.

The size for a field can be set using 3 different methods

  • set the size as an integer using the size attribute,
  • use a previously defined field as the size using the size attribute
  • read a set number of bits as an integer, using the resulting value as size using the read_bits attribute

When deriving BitReadSized the input size can be used in the size attribute as the input_size field.

Examples

use bitbuffer::BitRead;

#[derive(BitRead)]
struct TestStruct {
    foo: u8,
    str: String,
    #[size = 2] // when `size` is set, the attributed will be read using `read_sized`
    truncated: String,
    bar: u16,
    float: f32,
    #[size = 3]
    asd: u8,
    #[size_bits = 2] // first read 2 bits as unsigned integer, then use the resulting value as size for the read
    dynamic_length: u8,
    #[size = "asd"] // use a previously defined field as size
    previous_field: u8,
}
use bitbuffer::BitReadSized;

#[derive(BitReadSized, PartialEq, Debug)]
struct TestStructSized {
    foo: u8,
    #[size = "input_size"]
    string: String,
    #[size = "input_size"]
    int: u8,
}

Enums

The implementation can be derived for an enum as long as every variant of the enum either has no field, or an unnamed field that implements BitRead or BitReadSized

The enum is read by first reading a set number of bits as the discriminant of the enum, then the variant for the read discriminant is read.

For details about setting the input size for fields implementing BitReadSized see the block about size in the Structs section above.

The discriminant for the variants defaults to incrementing by one for every field, starting with 0. You can overwrite the discriminant for a field, which will also change the discriminant for every following field.

Examples

#
#[derive(BitRead)]
#[discriminant_bits = 2]
enum TestBareEnum {
    Foo,
    Bar,
    Asd = 3, // manually set the discriminant value for a field
}
#
#[derive(BitRead)]
#[discriminant_bits = 2]
enum TestUnnamedFieldEnum {
    #[size = 5]
    Foo(i8),
    Bar(bool),
    #[discriminant = 3] // since rust only allows setting the discriminant on field-less enums, you can use an attribute instead
    Asd(u8),
}
#
#[derive(BitReadSized, PartialEq, Debug)]
#[discriminant_bits = 2]
enum TestUnnamedFieldEnumSized {
    #[size = 5]
    Foo(i8),
    Bar(bool),
    #[discriminant = 3]
    #[size = "input_size"]
    Asd(u8),
}

Alignment

You can request alignment for a struct, enum or a field using #[align] attribute.

#
#[derive(BitRead)]
#[align] // align the reader before starting to read the struct
struct TestAlignStruct {
   #[size = 1]
   foo: u8,
   #[align] // align the reader before reading the field
   bar: u8,
}

It can also be applied to non-unit enum variants:

#
#[derive(BitRead)]
#[align] // align the reader before starting to read the enum
#[discriminant_bits = 2]
enum TestAlignEnum {
    Foo(u8),
    #[align] // align the reader before reading the variant (but after reading the discriminant)
    Bar(u8),
}

Endianness

If the struct that BitRead or BitReadSized is derived for requires a Endianness type parameter, you need to tell the derive macro the name of the type parameter used

#
#[derive(BitRead)]
#[endianness = "E"]
struct EndiannessStruct<'a, E: Endianness> {
    size: u8,
    #[size = "size"]
    stream: BitReadStream<'a, E>,
}

This is also required if you specify which endianness the struct has

#
#[derive(BitRead)]
#[endianness = "BigEndian"]
struct EndiannessStruct<'a> {
    size: u8,
    #[size = "size"]
    stream: BitReadStream<'a, BigEndian>,
}

Dependencies

~270–710KB
~16K SLoC