Go Tricks

Here’s a few useful go tricks.

Verify a type implements an interface

This is a common trick to ensure a type implements an interface. This is particularly useful when the interface your type is implementing is defined somewhere else, so just compiling without this might succeed even if the interface hasn’t been implemented. For example if you have a type that must implement the sql.Scanner interface.

After implementing the Scan(interface{}) error method on the Cat type the code will successfully compile. This is used all over, lots of different go projects.

Force other packages to use named fields

When using struct literals for types defined in other packages it’s best practice to use named fields so you don’t have to change your code when additive changes are made to the type in the other package. To force other packages to use named fields you can add an unexported empty struct{} field to your exported type. The AWS sdk uses this trick for all of their exported types. Trying to compile

package main

import (
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/s3"
)

func main() {
	s3.AbortIncompleteMultipartUpload{aws.Int64(42)}
}

results in

$ go build main.go
# command-line-arguments
./main.go:9:35: s3.AbortIncompleteMultipartUpload literal evaluated but not used
./main.go:9:45: implicit assignment of unexported field '_' in s3.AbortIncompleteMultipartUpload literal
./main.go:9:45: cannot use aws.Int64(42) (type *int64) as type struct {} in field value
./main.go:9:45: too few values in struct initializer

but using named fields like

s3.AbortIncompleteMultipartUpload{DaysAfterInitiation: aws.Int64(42)}

will compile.

Preventing other packages from implementing an interface

This is essentially the same trick as above but for interfaces. There are cases where you want to define an exported interface but don’t want other packages to be able to implement it. In particular, I have used this when working on a logging/metrics package with an exported list of variables which represent different metric types and a single exported function which can be called to record an instance of the metric. Callers of the package shouldn’t care how the metric is recorded internally and thus shoudn’t be able to define their own metrics. The implementation of that package was something along the lines of


var UserLogin Metric = userLogin{}

type Metric interface {
     String() string
     storeToDatabase(*db) error
}

func Collect(m Metric) error {
     ...
}

type userLogin struct {}

func (login userLogin) String() string { return "userLogin" }

func (login userLogin) storeToDatabase(db *db) error {
     ...
}

Since storeToDatabase is unexported, all instances of Metrics are guaranteed to be defined in this package which prevents any coupling between this package and other packages using this. In this particular example, it might just make sense to export different functions to collect different metric types, but if there are additional methods in the Metric interface and each metric must be a singleton this is one way to do it. This can also be useful in implementing the typical pattern for enum types to prevent having to worry about handling unexpected values (the day < Sunday || day > Saturday check in that example). Go’s protobuf implementation uses this technique to make sure certain types were autogenerated by the protobuf compiler.

Mapping between constants and values with an array

This last one is just something that I found interesting while reading some of the influxdb codebase mainly because it was the first time I saw the [...]string method for creating an array and also indexing an array with the index: value syntax. In their code for implementing the Influx query language they define a Token type and constants for each value.

type Token int

// These are a comprehensive list of InfluxQL language tokens.
const (
	// ILLEGAL Token, EOF, WS are Special InfluxQL tokens.
	ILLEGAL Token = iota
	EOF
	WS
	COMMENT

	literalBeg
	// IDENT and the following are InfluxQL literal tokens.
	IDENT       // main
	BOUNDPARAM  // $param
        ...
        BADREGEX    // `.*
	literalEnd

Later they define an array with a mapping from the constant value to the token value in their query language.

var tokens = [...]string{
	ILLEGAL: "ILLEGAL",
	EOF:     "EOF",
	WS:      "WS",
        ...

Since the array uses the constant values as the indicies the string values can just be looked like tokens[EOF]. Adding in the unexported literalBeg and literalEnd values makes it really easy to check whether the token is a certain type. They also define operatorBeg and operatorEnd constants and implement this function to check whether a token is an operator.

func (tok Token) isOperator() bool { return tok > operatorBeg && tok < operatorEnd }