-
Notifications
You must be signed in to change notification settings - Fork 18.4k
Description
Proposal: let binary.Write and binary.Read recognize common interfaces
Author(s): Eric Lagergren
Last updated: 2018-12-14
Abstract
Currently, binary.Write
and binary.Read
rejects struct fields, array elements, and top-level slices that don't have a fixed length. This limitation can make serializing structs time-consuming and error-prone. This proposal would remove this limitation, provided the non-fixed length type implements one of two interfaces.
Background
Attempting to use binary.Write
or binary.Read
to (de)serialize a struct, array, or top-level slice that contains a non-fixed length type (for example, []byte
) as a field or element results in an error. This is explicitly stated in the docs:
Data must be a fixed-size value or a slice of fixed-size values, or a pointer to such data.
Example 1, struct.
Example 2, array.
I originally encountered this limitation when I needed to use an API that required serialized structs. For example:
type Header struct {
Magic uint32
Len uint32
}
type T struct {
Header Header
Data []byte // len(Data) == Header.Len
}
var b bytes.Buffer
err := binary.Write(&b, binary.LittleEndian, T{ ... })
if err != nil {
// something ...
}
UseAPIThatIDontControl(b.Bytes())
Since T.Data
does not have a fixed length, the call to binary.Write
results in an error.
In order to serialize T
, I have to create a custom method:
func (t T) serialize() []byte {
var b bytes.Buffer
binary.Write(&b, binary.LittleEndian, t.Header) // This works, since Header only has fixed-length fields
b.Write(t.Data)
return b.Bytes()
}
UseAPIThatIDontControl((T{ ... }).serialize())
This specific case (T
) is quite simple. However, I have at least 5 other types, some of which look like this:
type G struct {
Header Header
Key1 []byte
X [20]uint32
Y [10]uint32
Key1 []byte
D [4]uint32
...
}
Creating a serialize
method for G
is time-consuming and error-prone:
func (g G) serialize() []byte {
// Error checking and field validation elided for brevity
var b bytes.Buffer
binary.Write(&b, binary.LittleEndian, g.Header)
binary.Write(&b, binary.LittleEndian, g.X)
binary.Write(&b, binary.LittleEndian, g.Y)
b.Write(g.Key1) // oops, this is out of order
b.Write(g.Key2)
binary.Write(&b, binary.LittleEndian, g.Q)
return b.Bytes()
}
Proposal
I propose we add two interfaces that allow user-specified encodings to and from []byte
for types that don't have a fixed length.
We need not use an already-existing interface, but encoding.BinaryMarshaler
and encoding.BinaryUnmarshaler
are good candidates.
Rationale
This proposal provides a simple way to serialize more complex structs, arrays, and top-level slices—something that has historically been time-consuming, error prone, and/or required code generation.
Compatibility
The change is Go 1 compatible if the interfaces are only checked after determining that the type does not have a fixed length. This is similar to: #12146
Implementation
TBD
Open issues (if applicable)
This idea has been mentioned before by Russ: #478 (comment)