Representing small network frames

Hey all. I'm trying to figure out a good model for representing related types of frames for a specific network. (It's CANbus, but that's not too important).

All the frames are about the same, fixed-size, and probably small enough to be Copy efficiently. For portability between platforms, there's a trait:

pub trait Frame : Sized {
    fn id(&self) -> u32;  // Address/ID
    fn data(&self) -> &[u8];
    // ...
}

So we implement this for all the frame types, DataFrame, ErrorFrame, RemoteFrame, etc. There's a lot of redundancy, but it's manageable.

Now we want to consider reading frames from the network:

fn receive_frame() -> Result<???, NetworkError>;

For performance reasons, we want to avoid dynamic/heap allocations. So maybe put them in an enum?

pub enum NetworkFrame {
    Data(DataFrame),
    Error(ErrorFrame),
    Remote(RemoteFrame),
    // ...
}

And, so:

fn receive_frame() -> Result<NetworkFrame, NetworkError>;

But now we start to get weird. For processing we need the type and can match the enum as needed. But for routing, etc, we just need some of the common information, like id(). And since every type in the enum is a Frame, then the enum can also implement Frame. But this gets really redundant:

impl Frame for NetworkFrame {
    fn id(&self) -> u32 {
        use NetworkFrame::*;
        match self {
            Data(frame) => frame.id(),
            Error(frame) => frame.id(),
            Remote(frame) => frame.id(),
        }
    }

    fn data(&self) -> &[u8] {
        use NetworkFrame::*;
        match self {
            Data(frame) => frame.data(),
            Error(frame) => frame.data(),
            Remote(frame) => frame.data(),
        }
    }

    // ...
}

So I'm wondering if I'm getting too crazy with this. The amount of redundance makes me think there must be an easier way to do this, even with the non-heap performance requirement. Like a way to specify that all variants in the enum are constrained to be Frame, and pass that through? Does anyone have any ideas?

Thanks.

The enum_dispatch crate can help cut down on the explicit redundancy via a procedural macro.

Nice. I love when the first answer is: "There's a crate for that!"
I'll have a look.

Another, dependency-free solution might be:

fn receive_frame(buf: &mut NetworkFrame) -> Result<&dyn Frame, NetworkError> {
    // `rand` only for illustration
    *buf = match rand::RngCore::next_u32(&mut rand::thread_rng()) % 3 {
        0 => NetworkFrame::Data(DataFrame),
        1 => NetworkFrame::Error(ErrorFrame),
        _ => NetworkFrame::Remote(RemoteFrame),
    };

    Ok(match buf {
        NetworkFrame::Data(fr) => fr,
        NetworkFrame::Error(fr) => fr,
        NetworkFrame::Remote(fr) => fr,
    })
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.