google / zerocopy

https://discord.gg/MAvWH2R6zk
Apache License 2.0
1.63k stars 104 forks source link

Make `#[derive(IntoBytes)]` accept more types #2064

Open joshlf opened 2 weeks ago

HonbraDev commented 2 weeks ago

Would #1112 also apply to dynamically-sized enums? I think I have a usecase for borrowing an enum with non-uniform field sizes as a byte slice but am not 100% sure I have the correct mindset, though I can already do this the other way around and interpret an appropriately-sized byte slice as a packet enum.

// `Packet` has inter-field padding
// the trait `PaddingFree<Packet, true>` is not implemented for `()`
// consider using `zerocopy::Unalign` to lower the alignment of individual fields
// consider adding explicit fields where padding would be
// consider using `#[repr(packed)]` to remove inter-field padding
// the trait `PaddingFree<Packet, false>` is implemented for `()`
// see issue #48214
#[derive(IntoBytes, KnownLayout)]
#[repr(u8)]
enum Packet {
    DoSomething = 0x10,
    DoSomethingWithValue([u8; 4]) = 0x20,
}

fn expected_behavior() {
    assert_eq!(Packet::DoSomething.as_bytes(), &[0x10]);
    assert_eq!(
        Packet::DoSomethingWithValue([0x12, 0x34, 0x56, 0x78]).as_bytes(),
        &[0x20, 0x12, 0x34, 0x56, 0x78],
    );
}
jswrenn commented 2 weeks ago

Unfortunately, no. "DST" in #1112 refers to types whose length is only known at runtime, and to which pointers carry extra length metadata. It wouldn't apply to your example, because Packet's size is known at runtime, and IntoBytes is a property of the entire type.

As the error message suggests, you can probably get your code to compile by adding an explicit padding field to Packet::DoSomething:

#[derive(IntoBytes, KnownLayout)]
#[repr(u8)]
enum Packet {
    DoSomething { _padding: [u8; 4] } = 0x10,
    DoSomethingWithValue([u8; 4]) = 0x20,
}

...but calling .as_bytes will still return all five bytes for Packet::DoSomething.

To work around this, you'll want to define your own "as bytes" mechanism that builds atop zerocopy's. Something like:

impl Packet {
    fn as_data_bytes(&self) -> &[u8] {
        let len = match self {
            Self::DoSomething { .. } => 1,
            Self::DoSomethingWithValue { .. } => 5,
        };
        &self.as_bytes()[..len]
    }
}