hashmismatch / packed_struct.rs

Bit-level packing and unpacking for Rust
MIT License
164 stars 30 forks source link

Using types for struct fields #86

Open T-Bakker opened 2 years ago

T-Bakker commented 2 years ago

Hi,

I am trying to replace modular_bitfield with packet_struct as that library is no longer maintained. As such, I am trying to use packet_struct with a non-struct type as a field:

pub type Field = Integer<u8, packed_bits::Bits::<4>>;

#[derive(PackedStruct, Debug, Clone, Copy, PartialEq)]
pub struct S {
    pub a: Field,
    pub b: Field,
}

This results in the following error: Couldn't determine the bit/byte width for this field.

Please note that I use the Field type in various structs in my code. Removing that type and simply specifying Interger<...> for each usage is undesired. I can also not use struct nesting as the struct require a byte boundary, which is not possible for me.

Explicitly specifying the bit positions results in a different error which I do not fully understand:

pub type Field = Integer<u8, packed_bits::Bits::<4>>;

#[derive(PackedStruct, Debug, Clone, Copy, PartialEq)]
#[packed_struct(bit_numbering="msb0")]
pub struct S {
    #[packed_field(bits="0..=3")]
    pub a: Field,
    #[packed_field(bits="4..=7")]
    pub b: Field,
}

Error: no function or associated item named unpack found for struct packed_struct::types::Integer<u8, Bits<4_usize>> in the current scope function or associated item not found in `packed_struct::types::Integer<u8, Bits<4_usize>>

Am I missing something? Is this possible somehow?

Thanks in advance, Tommas

rudib commented 2 years ago

Hi,

Unfortunately, type aliases are not supported in this way. A derive macro in Rust receives only the source of the particular struct, with no way to inspect the previously defined alias. It might be possible to support this exact scenario by implementing the packing traits on the Integer<> wrappers, but for now, this isn't supported. The definitions would also need to be even more explicit, as there is also a semi-hidden MSB/LSB wrapper around them.

However, if you want, you can define a structure just for this case, but then you need to give a hint what is the exact width of each individual field. A Deref implementation on the helper struct will also help with the ergonomics. I've written a small test and this seems to work fine for me. Not sure what you meant by byte boundary, but structures can be placed on a per bit level.

use packed_struct::prelude::*;

#[derive(PackedStruct, Debug, Clone, Copy, PartialEq, Default)]
#[packed_struct(bit_numbering="msb0")]
pub struct FieldStruct {
    #[packed_field(bits="4..")]
    pub data: Integer<u8, packed_bits::Bits::<4>>
}

#[derive(PackedStruct, Debug, Clone, Copy, PartialEq, Default)]
#[packed_struct(bit_numbering="msb0")]
pub struct S {
    #[packed_field(bits="0..4")]
    pub a: FieldStruct,
    #[packed_field(size_bits="4")]
    pub b: FieldStruct
}

#[test]
#[cfg(test)]
fn test_packed_type_alias() {
    let mut s = S::default();
    s.a.data = 0b1001.into();
    s.b.data = 0b0110.into();

    println!("data: {:#?}", s);
    let packed = s.pack().unwrap();
    println!("packed: {:#X?}", packed);

    let u = S::unpack(&packed).unwrap();
    assert_eq!(s, u);
}
rudib commented 2 years ago

This actually compiles:

pub type Field = MsbInteger<u8, packed_bits::Bits::<4>, Integer<u8, packed_bits::Bits::<4>>>;

#[derive(PackedStruct)]
#[packed_struct(bit_numbering="msb0")]
pub struct S {
    #[packed_field(bits="0..", size_bits="4")]
    pub a: Field,
    #[packed_field(size_bits="4")]
    pub b: Field
}

However those types were never designed to be used this way, so there are a couple of conversion traits that would have to be implemented to make things usable. Also, it's missing the critical traits like Copy/Clone/PartialEq/etc... So a valid feature request, but I'd have to dig a bit deeper how this interacts with the generated code.

cujomalainey commented 1 year ago

Would appreciate this implementation, i have a downstream upstream (code flow) of packed types and I rather they pass in the proper type size than cross my fingers im not truncating types