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

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Related Content


Type Conversion & struct tgs

We’ve already mentioned that struct tags are ignored when doing conversion between struct types. Now we see where that’s defined, along with an example: Conversions … Struct tags are ignored when comparing struct types for identity for the purpose of conversion: type Person struct { Name string Address *struct { Street string City string } } var data *struct { Name string `json:"name"` Address *struct { Street string `json:"street"` City string `json:"city"` } `json:"address"` } var person = (*Person)(data) // ignoring tags, the underlying types are identical I don’t think there’s much else to say about that, so let’s move on, and finish up this section.


Address operators

Let’s talk about addresses. Address operators For an operand x of type T, the address operation &x generates a pointer of type *T to x. The operand must be addressable, that is, either a variable, pointer indirection, or slice indexing operation; or a field selector of an addressable struct operand; or an array indexing operation of an addressable array. This leads to one of my great annoyances about the Go language.


Comparison operators, continued

Yesterday we started on comparison operators. Today we’ll continue, but there are a lot of types to cover, so I’ll break this down into a two or three parts, to keep each day’s email easily digestible. Recall that we had just been introduced to the concepts of ordered and comparable, and this is where we pick up… Comparison operators … These terms and the result of the comparisons are defined as follows: