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


Pointer Arithmetic

I mentioned a few days ago that pointer arithmetic is possible through the unsafe package. Here’s how to do it. Package unsafe … The function Add adds len to ptr and returns the updated pointer unsafe.Pointer(uintptr(ptr) + uintptr(len)) [Go 1.17]. The len argument must be of integer type or an untyped constant. A constant len argument must be representable by a value of type int; if it is an untyped constant it is given type int.


Package unsafe

Today we’re venturing into dangerous territory… the unsafe package! You can go years working in Go (as I have) without ever using unsafe, or just barely bumping into it. On the other hand, somd developers spend significant time using unsafe, to get the utmost performance out of their code, by working around the type system. Using unsafe can be powerful and beneficial. But… do it with the understanding that you’re giving up the benefits of memory and type safety, and cross-platform compatibility.


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.

Get daily content like this in your inbox!

Subscribe