Robbepop / modular-bitfield

Macro to generate bitfields for structs that allow for modular use of enums.
Apache License 2.0
155 stars 41 forks source link

Think about structs in structs #5

Closed dtolnay closed 3 years ago

dtolnay commented 5 years ago

I don't have anything figured out for this but it would be worth thinking about: what would it take to support factoring shared groups of fields like a common header? This would be in line with the theme of modularity.

struct H {
    a: B2,
    b: B3,
}

struct M {
    h: H,
    c: B11,
}

struct N {
    h: H,
    c: B19,
}
Robbepop commented 5 years ago

Good idea!

I think this could be easily implementable through #[derive(BitfieldSpecifier)] on structs that implements the Specifier trait similar as to how it is done for enums. The rest would just work out of the box if I imagine this correctly.

For this to work for header bitfield structs that are larger than 128 bits we would need to define bit specifiers for that region. For example we could use [u8; N] with N >= 33 for that:

impl modular_bitfield::Specifier for B129 {
    const BITS: usize = 129;
    type Base = [u8; 33];
    type Face = [u8; 33];
}

etc.

Robbepop commented 5 years ago

Another idea would be to use the derive macro to mark some structs as bitfield specifiers just as it is already been done for enums.

Example

#[derive(BitfieldSpecifier)]
struct Header {
    a: B2,
    b: B3,
}

Internally this struct will be of size sizeof<u8> + sizeof<u8> since both fields would just be translated to their respective underlying base type which is u8 for B2 and B3. This implies that B2 and B3 no longer can be represented as empty enum types but need to wrap or match their base type to capture the actual value later.

Unlike #[bitfield] annotated structs there is no longer a need for generating getters and setters since making the fields simply public is enough to ensure safe access.

/// Uses 3 bits in total.
#[derive(BitfieldSpecifier)]
struct HeaderHeader {
    x: B1,
    y: B2,
}

/// Uses 5 bits in total.
#[derive(BitfieldSpecifier)]
struct Header1 {
    a: B2,
    b: B3,
}

/// Uses 8 bits in total.
#[derive(BitfieldSpecifier)]
struct Header2 {
    hh: HeaderHeader,
    a: B2,
    b: B3,
}

/// Uses only header 1.
#[bitfield]
struct BaseA {
    h1: Header1,
    c: B11,
}

/// Uses only Header2.
///
/// Note that Header2 uses HeaderHeader internally.
#[bitfield]
struct BaseB {
    h: Header2,
    c: B19,
}

/// Uses all headers.
///
/// Note that this is effectively using HeaderHeader twice.
#[bitfield]
struct BaseC {
    h1: Header1,
    h2: Header2,
    h3: HeaderHeader,
    a: B7,
    b: B9,
}

The next code snippets demonstrates what code arround a bitfield specifier struct is generated:

/// Uses 3 bits in total.
#[derive(BitfieldSpecifier)]
struct Header {
    x: B1,
    y: B2,
}

impl modular_bitfield::Specifier for Header {
    const BITS: usize =
          <B1 as modular_bitfield::Specifier>::BITS
        + <B2 as modular_bitfield::Specifier>::BITS;
    type Base = <[(); <B1 as modular_bitfield::Specifier>::BITS
        + <B2 as modular_bitfield::Specifier>::BITS] as modular_bitfield::SpecifierBase>::Base;
    type Face = Header;
}

impl modular_bitfield::FromBits<<Header as modular_bitfield::Specifier>::Base> for Header {
    fn from_bits(bits: modular_bitfield::Bits<<Header as modular_bitfield::Specifier>::Base>) -> Self {
        // To be determined ...
    }
}

impl modular_bitfield::IntoBits<<Header as modular_bitfield::Specifier>::Base> for Header {
    fn into_bits(self) -> modular_bitfield::Bits<<Header as modular_bitfield::Specifier>::Base> {
        // To be determined ...
    }
}
Robbepop commented 3 years ago

With the most recent improvements in the backend of the modular_bitfield crate I now see a more or less easy solution towards supporting structs in structs.

For this we need to add another attribute #[specifier] to the #[bitfield] proc. macro attribute. Using the specifier attribute allows for bitfield structs that also implement the Specifier trait, also it allows them to not have bit widths divisible by 8. We could introduce yet another attribute to control this aspect for non-specifier bitfields as well but maybe better for a follow-up PR.

The following example demonstrates what code needs to be generated for a situation where we have a Header bitfield that also is a specifier:

#[bitfield(specifier = true)]
pub struct Header {
    a: B2,
    b: B3,
}

#[bitfield]
pub struct Generated {
    pub header: Header,
    pub rest: B3, // To make it divisible by 8.
}

This would generate approximately this code for the Specifier trait implementation:

impl modular_bitfield::Specifier for Header {
    const BITS: usize = 5;

    type Bytes = u8;
    type InOut = Self;

    fn into_bytes(
        value: Self::InOut,
    ) -> Result<Self::Bytes, OutOfBounds> {
        Ok(u8::from_le_bytes(value.data))
    }

    fn from_bytes(
        bytes: Self::Bytes,
    ) -> Result<Self::InOut, InvalidBitPattern<Self::Bytes>>
    {
        if bytes > ((0x01 << Self::BITS) - 1) {
            return Err(InvalidBitPattern::new(bytes))
        }
        Ok(Self {
            data: bytes.to_le_bytes(),
        })
    }
}
Robbepop commented 3 years ago

WIP PR: https://github.com/Robbepop/modular-bitfield/pull/29

Robbepop commented 3 years ago

Closed as #29 has just been merged.