Go struct tags with underscore before function names

Those are called blank-fields because the blank identifier is used as the field name.

They cannot be referred to (just like any variable that has the blank identifier as its name) but they take part in the struct’s memory layout. Usually and practically they are used as padding, to align subsequent fields to byte-positions (or memory-positions) that match layout of the data coming from (or going to) another system. The gain is that so these struct values (or rather their memory space) can be dumped or read simply and efficiently in one step.

@mkopriva’s answer details what the specific use case from the question is for.

A word of warning: these blank fields as “type-annotations” should be used sparingly, as they add unnecessary overhead to all (!) values of such struct. These fields cannot be referred to, but they still require memory. If you add a blank field whose size is 8 bytes (e.g. int64), if you create a million elements, those 8 bytes will count a million times. As such, this is a “flawed” use of blank fields: the intention is to add meta info to the type itself (not to its instances), yet the cost is that all elements will require increased memory.

You might say then to use a type whose size is 0, such as struct{}. It’s better, as if used in the right position (e.g. being the first field, for reasoning see Struct has different size if the field order is different and also Why position of `[0]byte` in the struct matters?), they won’t change the struct’s size. Still, code that use reflection to iterate over the struct’s fields will still have to loop over these too, so it makes such code less efficient (typically all marshaling / unmarshaling process). Also, since now we can’t use an arbitrary type, we lose the advantage of carrying a type information.

This last statement (about when using struct{} we lose the carried type information) can be circumvented. struct{} is not the only type with 0 size, all arrays with 0 length also have zero size (regardless of the actual element type). So we can retain the type information by using a 0-sized array of the type we’d like to incorporate, such as:

type CustomLabel struct {
    _ [0]func() `constructor:"init"`
    _ [0]string `property:"text"`
}

Now this CustomLabel type looks much better performance-wise as the type in question: its size is still 0. And it is still possible to access the array’s element type using Type.Elem() like in this example:

type CustomLabel struct {
    _ [0]func() `constructor:"init"`
    _ [0]string `property:"text"`
}

func main() {
    f := reflect.ValueOf(CustomLabel{}).Type().Field(0)
    fmt.Println(f.Tag)
    fmt.Println(f.Type)
    fmt.Println(f.Type.Elem())
}

Output (try it on the Go Playground):

constructor:"init"
[0]func()
func()

For an overview of struct tags, read related question: What are the use(s) for tags in Go?

Leave a Comment