Robbepop / modular-bitfield

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

[Question] Polymorphic bitfileds #89

Open akprasad opened 1 year ago

akprasad commented 1 year ago

Thank you for the wonderful library.

I don't know how to name what I'm looking for, so I'm calling them polymorphic bitfields by analogy with a similar concept I've seen in SQL. The idea is that the bitfield has two parts: a type field, and a payload that we interpret differently depending on the type.

As a contrived but simple example, consider a u16 where the first bit is 0 if the remainder should be interpreted as a u15 and a 1 if the remainder should be interpreted as fiveu3s.

Currently, I manage this behavior as follows:

#[derive(BitfieldSpecifier)]
#[bits = 1]
enum PayloadType {
  TypeOne,
  TypeTwo,
}

#[bitfield]
struct Data {
    type_: PayloadType,
    payload: B31,
}

#[bitfield(bits = 31)]
pub struct PackedTypeOne {
    ...
}

#[bitfield(bits = 31)]
pub struct PackedTypeTwo {
    ...
}

impl Data {
    pub fn unwrap_as_type_one(&self) -> PackedTypeOne {
      PackedTypeOne::from_bytes(self.payload().to_be_bytes())
    }
    pub fn unwrap_as_type_two(&self) -> PackedTypeTwo {
      PackedTypeTwo::from_bytes(self.payload().to_be_bytes())
    }
}

But this feels clumsy to me, as it seems that the caller can unwrap_as_type_one even if the payload actually corresponds to TypeTwo.

I read through the documentation for modular_bitfield, and while I learned a lot of wonderful things about this library, I'm not sure how to best implement what I describe above. I would be grateful for any help you could provide.

hecatia-elegua commented 1 year ago

First, this is less clumsy for the caller:

enum PackedType {
    One(PackedTypeOne),
    Two(PackedTypeTwo)
}

impl Data {
    pub fn unwrap(&self) -> PackedType {
        if matches!(self.type_(), TypeOne) {
            PackedType(PackedTypeOne::from_bytes(self.payload().to_be_bytes()))
        } else {
            PackedType(PackedTypeTwo::from_bytes(self.payload().to_be_bytes()))
        }
    }
}

(Also see how this duplicates PayloadType's variants)

Second, I basically have the same problem, thought about this a bit and I think the nicest semantics would be to use a normal rust enum:

#[bitfield(bits = 32)]
struct Data {
    type_: B1, //or bool, or enum with 1 bit, edit: though as this is part of PackedType already, I think it should be private?
    #[discriminant = type_]
    payload: PackedType,
}

#[..?] //not sure
enum PackedType {
    One(PackedTypeOne),
    Two(PackedTypeTwo)
}

#[bitfield(bits = 31)]
pub struct PackedTypeOne { ... }
#[bitfield(bits = 31)]
pub struct PackedTypeTwo { ... }

with #[discriminant = ...] being the field where we want to store the enums discriminant information. How would this work low-level? I don't know. PackedType would then kind of be the "union" part of the tagged union, type_ would be the tag.