Binary serialisation made even easier: no boilerplate with Cerealed

I abhor boilerplate code and duplication. My main governing principle in programming is DRY. I tend not to like languages that made me repeat myself a lot.

So when I wrote a serialisation library, I used it in real-life programs and made sure I didn’t have to repeat myself as I had to when I wrote similar code in C++. The fact that D allows me to get the compiler to write so much code for me, easily, is probably the main reason why it’s my favourite language.

But there were still patterns of emerging in the networking code I wrote that started to annoy me. If I’m reaching for nearly identical code for completely different networking protocol packets, then there’s a level of abstraction I should be using but failed to notice beforehand. The patterns are very common in networking, and that’s when part of the packet contains either the total length in bytes, or length minus a header size, or “number of blocks to follow”. This happens even in something as simple as UDP. With the previous version of Cerealed, you’d have to do this:

struct UdpPacket {
    ushort srcPort;
    ushort dstPort;
    ushort length;
    ushort checksum;
    @NoCereal ubyte[] data; //won't be automatically (de)serialised

    //this function is run automatically at the end of (de)serialisation
    void postBlit(C)(ref C cereal) if(isCereal!C) {
        int headerSize = srcPort.sizeOf + dstPort.sizeof + length.sizeof + checksum.sizeof;
        cereal.grainLengthedArray(data, length - headerSize); //(de)serialise it now
    }
}

Which, quite frankly, is much better than what usually needs to be done in most other languages / frameworks. This was still too boilerplatey for me and got got old fast. So now it’s:

struct UdpPacket {
    //there's an easier way to calculate headerSize but explaining how'd make the blog post longer
    enum headerSize = srcPort.sizeOf + dstPort.sizeof + length.sizeof + checksum.sizeof; 
    ushort srcPort;
    ushort dstPort;
    ushort length; 
    ushort checksum; 
    @LengthInBytes("length - headerSize") ubyte[] data; 
    //code? We don't need code 
}

That “length – headerSize” in @LengthInBytes? That’s not a generic name, that’s a compile-time string that refers to the member variable and manifest constant (enum) declared in the struct. The code to actually do the necessary logic is generated at compile-time from the struct declaration itself.

Why write code when I can get the compiler to do it for me? Now try doing that at compile-time in any other language! Well, except Lisp. The answer to “can I do ___ in Lisp” is invariably yes. And yet, not always this easily.

Cerealed is on github and is available as a dub package.

Advertisements

One thought on “Binary serialisation made even easier: no boilerplate with Cerealed

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: