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 }