Reader Question: Understanding (*T)(nil) syntax

February 13, 2023

I got a great email from a reader (I didn’t ask their permission to share, so keeping it anonymous) in response to last week’s message Interface variables, pointing out the somewhat essoteric syntax mentioned at the end of the example section:

x = v              // x has value (*T)(nil) and dynamic type *T

The (*T)(nil) syntax is easily confusing. It has been for me, too. So let’s dig into that, and hopefully quell that confusion, once and for all!

Now, the full details of what’s going on here are still to come when we get to the section of the spec on conversions, so this will serve as a sneak peak.

If you’ve been reading for a while, you may recall a couple of weeks ago when I talked about typed and untyped constants. In that post, I demonstrated an explicitly typed constant:

const isValid = bool(true)

This example shows a case of a type conversion. bool(true) serves as a type conversion, converting the contents of the parenthesis (i.e. true) to a bool type (or if conversion is impossible, it will cause a compile-time error).

I believe this is fairly intuitive to most people. We see this sort of type conversion all over the place in Go. Here are a few random examples:

var x = int(123)      // Convert from untyped literal `123` to int
var y = int64(x)      // Convert from int to int64
var z = []byte("foo") // Convert an untyped string literal to a byte slice

Now, as we’ll actually see tomorrow, the name of a type can also be expressed in parenthesis. This means this is equivalent to the above code:

var x = (int)(123)      // Convert from untyped literal `123` to int
var y = (int64)(x)      // Convert from int to int64
var z = ([]byte)("foo") // Convert an untyped string literal to a byte slice

I hope the pattern is beginning to emerge now…

Most of the time these parenthesis are completely superfluous. But occasionally, they’re actually meaningful. As when dealing with pointer types! Here’s a contrived example:

var x = int(123)
var y = &x
var z = (*int)(y)

Without the parenthesis around *int, the * is interpreted as the multiplication operator.

So what does (*T)(nil) mean?

It’s a type conversion expression to convert nil to the type of *T, or a pointer to T. (T is commonly used as a stand-in for any type in documentation).

This means that that these two variable declarations are equivalent. They create a variable of type *int, with a value of nil.

var x *int
var y = (*int)(nil)

So why would you ever want the latter syntax? Well, because it doesn’t require assignment to a variable. This is commonly seen in conjunction with the blank identifier (which we’ll get to in time), to perform a sort of compile-time assertion that a type implements a desired interface (if this doesn’t make sense to you yet, don’t worry, we’re really jumping ahead here).

// Ensures that *MyCustomType satisfies the json.Marshaler interface, without
// allocating any memory.
var _ json.Marshaler = (*MyCustomType)(nil)
Share this

Related Content

Empty structs

We finally we have enough knowledge for the EBNF format not to seem completely foreign, so let’s jump back and take a look at that, with the examples provided in the spec… Struct types … StructType = "struct" "{" { FieldDecl ";" } "}" . FieldDecl = (IdentifierList Type | EmbeddedField) [ Tag ] . EmbeddedField = [ "*" ] TypeName [ TypeArgs ] . Tag = string_lit . // An empty struct.

Struct tags

Struct types … A field declaration may be followed by an optional string literal tag, which becomes an attribute for all the fields in the corresponding field declaration. An empty tag string is equivalent to an absent tag. The tags are made visible through a reflection interface and take part in type identity for structs but are otherwise ignored. struct { x, y float64 "" // an empty tag string is like an absent tag name string "any string is permitted as a tag" _ [4]byte "ceci n'est pas un champ de structure" } // A struct corresponding to a TimeStamp protocol buffer.

Struct method promotion

Yesterday we saw an example of struct field promotion. But methods (which we haven’t really discussed yet) can also be promoted. Struct types … Given a struct type S and a named type T, promoted methods are included in the method set of the struct as follows: If S contains an embedded field T, the method sets of S and *S both include promoted methods with receiver T. The method set of *S also includes promoted methods with receiver *T.