When would you use encoding/binary, encoding/gob or protobuf for serialization? What are the byte-order and compatibility pitfalls?
Question 546HardGo 1.22 to 1.25
- encoding/binary: fixed-layout binary protocols and file formats (network headers, on-disk records, hashing inputs) where you control every byte. There is no schema and no versioning.
- encoding/gob: Go-to-Go only (net/rpc, caches). It is a self-describing stream: type information is sent once per Encoder, so it is efficient for long streams but bloated for single small messages. Fields are matched by name, so adding or removing fields is tolerated. Concrete types sent through interface fields need
gob.Register. It cannot encode channels or functions, and unexported fields are ignored. - protobuf: cross-language APIs, gRPC, and long-lived storage. Its schema evolution works through field numbers: never reuse a number, mark removed ones
reserved, and remember that proto3 does not send zero values, so useoptionalwhen you need to know whether a field was set.
type Header struct {
Magic uint32
Version uint16
Flags uint16
Length uint64
}
var buf bytes.Buffer
if err := binary.Write(&buf, binary.BigEndian, Header{Magic: 0xCAFEBABE, Version: 1, Length: 42}); err != nil {
log.Fatal(err)
}
fmt.Println(buf.Len(), binary.Size(Header{})) // 16 16 (no padding written)
b := binary.BigEndian.AppendUint32(nil, 0xCAFEBABE) // allocation-friendly
b = binary.AppendUvarint(b, 300) // varint: 2 bytes
v := binary.BigEndian.Uint32(b[:4])
fmt.Printf("%x\n", v) // cafebabe
Pitfalls:
- The network convention is BigEndian, while x86 and ARM hardware are little-endian. Mixing the two up corrupts data silently.
binary.Writerejectsint,uint, strings and slices of structs that contain them. Use fixed-size types.- Casting memory with
unsafeis not portable because of padding and endianness. - gob streams are only valid within one Encoder/Decoder pair.
- Changing a protobuf field's type or number breaks old readers.
More on More Standard Library Essentials
- Q544How do you parse CLI flags with the flag package? How do subcommands work, and why is flag.Parse in init() a bug?
- Q545Compare strconv with fmt for number/string conversion. What do strconv.ParseInt's base and bitSize arguments do, and how do you check for range errors?
- Q547How do signals work in Go (os/signal)? Why must the channel passed to signal.Notify be buffered?