Skip to content
Daz Wilkin edited this page Aug 5, 2020 · 6 revisions

One of the most useful features of MessagePack is its support for arbitrary type system extensions. msgp uses MessagePack's extension features to add support for Go's time.Time, complex64, and complex128 types. Additionally, msgp allows users to define their own extension types.

On the wire, a MessagePack ext is simply a tuple of (int8, []byte), where the int8 maps to a particular application-defined type.

The Extension interface

The first step to implementing your own MessagePack type is implementing the msgp.Extension interface for that type:

type Extension interface {
    ExtensionType() int8
    Len() int
    MarshalBinaryTo([]byte) error
    UnmarshalBinary([]byte) error
}
ExtensionType()

ExtensionType() should always return the same value for a given concrete type, and simply identifies the type of the object whose data follows. Keep in mind that msgp uses types 3, 4, and 5 for complex64, complex128, and time.Time, respectively, and that types < 0 are reserved by the MessagePack spec.

Len()

Len() should return the encoded length of the object, in bytes, given its current state.

MarshalBinaryTo([]byte)

MarshalBinaryTo([]byte) should put the binary-encoded value of the object into the supplied []byte. It will always be the case that the length of the supplied []byte is whatever Len() returned.

UnmarshalBinary([]byte)

UnmarshalBinary should decode the value of the object from the supplied []byte.

Registering Extensions

The next step to using an extension with msgp is registering the extension with the package during initialization. Although this step is optional, it is highly recommended, as it allows the decoding of interface{} to return the appropriate concrete type when it recognizes the extension type, and it also allows the JSON translation to use the canonical JSON form of your object when translating. (Bonus points if your type already implements the json.Marshaler interface.)

Generating Code

In order for the code generator to recognize that you want a type to be treated as a msgp.Extension instead of its concrete type, you must annotate the field with "extension" directly:

type Thing struct {
    Num Numbers `msg:"num,extension"`
}

type Numbers [4]float64

func (n *Numbers) ExtensionType() int8            { /* ... */ }
func (n *Numbers) Len() int                       { /* ... */ }
func (n *Numbers) MarshalBinaryTo(b []byte) error { /* ... */ }
func (n *Numbers) UnmarshalBinary(b []byte) error { /* ... */ }

A Worked Example

package main

import (
    "github.com/tinylib/msgp/msgp"
)

func init() {
    // Registering an extension is as simple as matching the
    // appropriate type number with a function that initializes
    // a freshly-allocated object of that type
    msgp.RegisterExtension(99, func() msgp.Extension { return new(RGBA) } )
}

// RGBA will be the concrete type
// of our new extension
type RGBA [4]byte

// Here, we'll pick an arbitrary number between
// 0 and 127 that isn't already in use
func (r *RGBA) ExtensionType() int8 { return 99 }

// We'll always use 4 bytes to encode the data
func (r *RGBA) Len() int { return 4 }

// MarshalBinaryTo simply copies the value
// of the bytes into 'b'
func (r *RGBA) MarshalBinaryTo(b []byte) error {
    copy(b, (*r)[:])
    return nil
}

// UnmarshalBinary copies the value of 'b'
// into the RGBA object. (We might want to add
// a sanity check here later that len(b)==4.)
func (r *RGBA) UnmarshalBinary(b []byte) error {
    copy((*r)[:], b)
    return nil
}

Once we've written the boilerplate above, using our new type is as simple as:

type ColoredBox struct {
    Height int  `msg:"height`
    Width  int  `msg:"width"`
    Color  RGBA `msg:"color,extension"`
}

Additionally, if we decide we want RGBA represented a particular way in JSON, all we need to do is implement the json.Marshaler interface, and the translator methods will use this over a more generic representation:

func (r *RGBA) MarshalJSON() ([]byte, error) {
    b := *r
    return []byte(fmt.Sprintf("[%d, %d, %d, %d]", b[0], b[1], b[2], b[3])), nil
}