Skip to content

Preprocessor Directives

Uladzimir Trehubenka edited this page Aug 12, 2025 · 12 revisions

You can make tweaks to how the code generator generates methods using directives in your source code comments. All directives take the form

//msgp:directive [arg1] [arg2] [arg3]...

much like the gc compiler directives.

There are two flavors of directives: global and pass-specific.

Global Directives

Ignore

//msgp:ignore Type1 Type2 Type3

Ignore tells the code generator not to generate methods for the list of types supplied.

Tuple

//msgp:tuple TypeA

type TypeA struct {
    Left  float64
    Right float64
}

The msgp:tuple directive tells the generator to generate code for the struct so that it is serialized as an array instead of a map. In other words, TypeA{1.0, 2.0} above would be encoded as

[1.0,2.0]

instead of

{"Left":1.0,"Right":2.0}

For smaller objects, tuple encoding can yield serious performance improvements.

VarTuple

//msgp:vartuple UserApiV2

type UserApiV2 struct {
    ID    string // ApiV1 field
    Name  string // ApiV2 field
}

The msgp:vartuple directive tells the generator to generate code as the directive tuple does. The difference is that the directive tuple generates an array with a strict check of the fields' count; for example, you cannot unmarshal UserApiV2 from msgpack data ["0001"] or msgpack data ["0001", "Bob", "[email protected]"]. Whereas the directive vartuple generates methods UnmarshalMsg() and DecodeMsg() in a way when msgpack array fields' count may be different from struct fields' count. So if we have for example:

//msgp:vartuple UserApiV1

type UserApiV1 struct {
    ID    string // ApiV1 field
}

//msgp:vartuple UserApiV3

type UserApiV3 struct {
    ID    string // ApiV1 field
    Name  string // ApiV2 field
    Email string // ApiV3 field
}

it's possible to use Marshal/Encode UserApiV1/UserApiV2/UserApiV3 and then use Unmarshal/Decode UserApiV1/UserApiV2/UserApiV3 without any error because these structures are backward compatible.

Shim

//msgp:shim Enum as:string using:(Enum).String/parseString

type Enum byte

const(
    A Enum = iota
    B
    C
    D
    invalid
)

func (e Enum) String() string {
    switch e {
    case A:
        return "A"
    case B:
        return "B"
    case C:
        return "C"
    case D:
        return "D"
    default:
        return "<invalid>"
    }
}

func parseString(s string) Enum {
    switch s {
    case "A":
        return A
    case "B":
        return B
    case "C":
        return C
    case "D":
        return D
    default:
        return invalid
    }
}

The shim directive lets you inline a type-conversion function for a user-defined type in order to have it encode and decode differently than the default for its concrete type. In the example above, we're using the shim directive to translate a const-iota block into strings for encoding and decoding. Note that the as: argument must take a "base" type (a built-in, []byte, interface{}, msgp.Extension or a type already processed by the code generator.)

Example shimming an external types to JSON:

//msgp:shim host.InfoStat as:[]byte using:asJSON/parseJSONInto[host.InfoStat]
//msgp:shim host.TemperatureStat as:[]byte using:asJSON/parseJSONInto[host.TemperatureStat]

func asJSON(v any) []byte {
	b, _ := json.Marshal(v)
	return b
}

func parseJSONInto[T any](b []byte) T {
	var t T
	json.Unmarshal(b, &t)
	return t
}

Note there is no error checking on shimmed types.

Replace

the replace directive makes it easier to serialize foreign types.

Example usage with github.com/google/uuid

  package main

  import "github.com/google/uuid"

  //go:generate msgp

  //msgp:replace uuid.UUID with:UUID
  type UUID [16]byte

  // Or like that
  //msgp:replace uuid.UUID with:[16]byte

  type User struct {
    ID uuid.UUID
  }

This can also be used to replace serialization of external types with internal types.

For example //msgp:replace cpu.TimesStat with:cpuTimesStat will replace an external type cpu.TimesStat with a local cpuTimesStat, which should be a copy of the external struct, that has generated functions.

Pass-Specific Directives

A pass-specific directive operates on one particular code generation pass. They take the form

//msgp:passname directives [arg1] [arg2] ...

The valid pass names are as follows:

  • encode
  • decode
  • marshal
  • unmarshal
  • size
  • test

Ignore

The ignore directive can be applied to a particular combination of pass and type when invoked like:

//msgp:encode ignore Type1 Type2...

Methods that are ignored also do not produce test cases. (The code generator is aware of the dependency.)

Ignore can be particularly useful when you don't want the code generator to clobber an existing manually-written implementation of one of the methods.

Tag

Using the tag directive allows reading MessagePack field names from a custom struct tag via the //msgp:tag {tagname} directive. If the custom tag isn't present for a field, msg and msgpack are checked next.

Example:

//go:generate msgp
//msgp:tag json

// Heartbeat - A heartbeat message
type Heartbeat struct {
	Service string `json:"service"` // "service" is used for both json and msgp
	Message string `json:"message"`
}
Clone this wiki locally