Go: Pointer vs Value
In A Tour of Go, it states “Go has pointers. A pointer holds the memory address of a value.” When you design your data structure in Go, you have to decide between using a pointer or not. There’s no clear rule of thumb for it.
I had been reading the Go source code to AWS’s client library for DynamoDB. For a while, I had been annoying with their API design, which looks like this:
func (c *DynamoDB) Query(input *QueryInput) (*QueryOutput, error)
type QueryInput struct {
ConsistentRead *bool `type:"boolean"`
IndexName *string `min:"3" type:"string"`
Limit *int64 `min:"1" type:"integer"`
ScanIndexForward *bool `type:"boolean"`
TableName *string `min:"3" type:"string" required:"true"`
...
}
queryInput.TableName = aws.String("my-table")
queryInput.ConsistentRead = aws.Bool(false)
I couldn’t help but ask why everything is a pointer. In Java, nearly everything is a pointer because Java doesn’t have a distinction between pointer and non-pointer objects. In Java, every object is a reference. Every object result in allocation on the heap and a subsequent garbage collection.
In contrast, Go allows you to specify whether something is a pointer or a concrete struct; You may explicitly avoid heap allocation by choosing concrete structs. Thus, from the perspective of a Go programmer, AWS’s SDK design is not only ugly – (“foo = aws.Boolean(false)” instead of “foo = false”), but adds unnecessary GC pressure. So why would they do this?
But today I understood a subtle advantage to pointers. A pointer implicitly state whether a field was intentionally set or not. The AWS SDK has code like this:
if s.Limit != nil && *s.Limit < 1 {
invalidParams.Add(request.NewErrParamMinValue("Limit", 1))
}
In other words, the use of pointers allow one differentiate between “please use the default” vs “I want to use the empty
value”. If Limit
had been a non-pointer, then its empty value would’ve been zero. There is no way for the program to
know whether a Limit was explicitly set to zero (which is an error) or intentionally left blank (which is an ok way to
ask for the default value). Thus, the use of pointers in the AWS SDK is pervasive because it’s simpler to make
everything a pointer and not have to retrofit to support defaults. Of course, I should’ve known this – it is common in
Java to design POJOs using Integer
instead of int
to allow one to specify “this was not set”.
While I can’t say that the rule-of-thumb for Go is “prefer pointers to values”, I can certainly see why they are frequently used in the AWS SDK.