nil pointers

March 24, 2023

Yesterday we learned about pointers. But beyond what the spec says, there are a few notes worth calling out.

To start with, as mentioned in the spec, the zero value for a pointer is nil. But it’s still a typed nil value, as we can see by using the %T printf argument, which displays the variables type. Expanding on yesterday’s example:

	var x int
	var y *int
	var z **int
	// Prints: 0 <nil> <nil> <nil>
	fmt.Println(x, y, z, youMustBeKidding)
	// Prints: int *int **int **********int
	fmt.Printf("%T %T %T %T\n", x, y, z, youMustBeKidding)

Why is this valuable?

One common use for this is to distinguish between a default value and an unset value, for the base type.

To illustrate, let’s imagine we’re receiving some JSON data in a REST API, and we want to know whether a field is omitted, versus set to the default value:

type query struct {
	// Limit limits the number of results returned.
	// Default is 10. 0 means return all results.
	Limit int `json:"limit"`

With the above struct, there’s no way to tell the difference between a JSON payload of {} and {"limit":0}. Both retsult in a value of 0 for the Limit field:

	var q struct {
		Limit int `json:"limit"`
	_ = json.Unmarshal([]byte(`{}`), &q)
	fmt.Println(q.Limit) // Prints: 0

	_ = json.Unmarshal([]byte(`{"limit":0}`), &q)
	fmt.Println(q.Limit) // Prints: 0

So the solution is to use a pointer:

	var q struct {
		Limit *int `json:"limit"`
	_ = json.Unmarshal([]byte(`{}`), &q)
	fmt.Println(q.Limit) // Prints: <nil>

	_ = json.Unmarshal([]byte(`{"limit":0}`), &q)
	fmt.Println(q.Limit) // Prints: 0xc00001c0d0
  fmt.Println(*q.Limit) // Prints: 0

As you can see in the above example, we can now see that it’s apparent when the value is omitted, versus when it’s set to the default.

This approach does require dereferencing the pointer value, of course, to get at the underlying value, which you see on the last line of the above example, with the * character (*q.Limit).

Share this

Related Content

Zero values of slices and maps

Composite literals … Note that the zero value for a slice or map type is not the same as an initialized but empty value of the same type. Let’s look at examples so we can understand exactly what is being stated here. Recall that when you declare a variable of a given type, the default value is the zero value for the type: var s1 []string // Zero value for slice var m1 map[string]int // Zero value for map What the spec is telling us is that the zero value of a slice or map is not the same as an initialized, but empty, slice or map:

Taking the address of literals

Composite literals … Taking the address of a composite literal generates a pointer to a unique variable initialized with the literal’s value. var pointer *Point3D = &Point3D{y: 1000} This particular feature of the language is immensely useful. Without it, the above code snippet would become much longer: var point Point3D = Point3D{y: 1000} var pointer *Point3D = &point In fact, this is the exact situation we are in for non-composites.

Limitations on methods

Today I’ll be love coding again. I hope you can join me! I’ll be working on the backlog for my open-source CouchDB SDK, to add a long-missing feature. Join me to see how many mistakes a senior Go dev makes while coding. Method declarations … A receiver base type cannot be a pointer or interface type and it must be defined in the same package as the method. This tiny sentence has three distinct cases to consider.