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