Closed dtolnay closed 3 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.
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.
#[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 ...
}
}
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(),
})
}
}
Closed as #29 has just been merged.
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.