Go: Experience Report: Pointers

Problem summary

In Go, pointers get used for two things:

  1. Indicate the lack of a value.
  2. Pass ownership of a value.

When I make a pointer variable I often only want to do one of the above. Regardless of my intent, the variable I create carries both intentions in its declaration. Because the declaration of the variable does not communicate my intent, this is knowledge that the reader must infer from what my application does with the variable and is another piece of information that must live in the readers head rather than in the code. At best it lives in the comments. This has made it difficult for me to determine what the intent was when I’ve read others Go code, and it has confused others who have read my code.

Problem analysis

Pointers are:

They are often used to communicate a lack of value in JSON/XML deserialized into structs, but then introduce the additional meaning and burden that these fields also transfer ownership.

It is not possible to variables/fields that only transfer ownership or only indicate a lack of value, but I frequently declare pointer variables that need only one of these features. Most of the time, to indicate a lack of value is the one where I feel the most pain because inheriting ownership transfer has many several side effects.

Example: Indicate a lack of value

Package: github.com/lionelbarrow/braintree-go

type Plan struct {
	XMLName               string       `xml:"plan"`
	ID                    string       `xml:"id"`
	//...
	TrialDuration         *int         `xml:"trial-duration"`
	//...
}

In the Plan struct taken from the braintree package, the TrialDuration field is optional and may be nil. If it is nil it is indicative that there is no trial duration defined.

However, because TrialDuration is a pointer there are several nasty side effects:

Example: Pass ownership of a value

Package: github.com/lionelbarrow/braintree-go

func NewWithHttpClient(env Environment, merchantId, publicKey, privateKey string, client *http.Client) *Braintree {
	return &Braintree{credentials: newAPIKey(env, merchantId, publicKey, privateKey), HttpClient: client}
}

In the NewWithHttpClient function taken from the braintree package, the *Braintree return value is declared as a pointer because the function wishes to pass ownership of the value held in memory to the caller and not copy the value pointed at. The intent is that the *Braintree would always point to a value.

However, because Braintree is returned as a pointer there is one inconvenient side effect:

Example: A work around for lack of value

Package: database/sql

type NullInt64 struct {
	Int64 int64
	Valid bool // Valid is true if Int64 is not NULL
}

The null struct is used in some packages to indicate a lack of value by defining an additional type for every type that may lack value.

The NullInt64 defined in database/sql demonstrates we can represent the lack of value (null) by wrapping our value inside another struct with a side-car bool to indicate if there is a value or if it is empty.

Because NullInt64 is returned as a pointer there are inconvenient side effects:

Closing remarks

Software engineers use pointers in Go to indicate the lack of value or pass ownership, but the intent of which is lost, and the side-effects of both must be adopted, because a single mechnism is used for both.

Experiments

Comments

comments powered by Disqus