What’s New in Go 1.22: reflect.TypeFor
The first release candidate for Go 1.22 is out, which means it’s almost time for the final release and it is time for me to blog about what I worked on this cycle. As usual, my contributions were small, but they were mine, so I’m going to talk about them from a behind-the-scenes perspective. First up is reflect.TypeFor.
Here is the entire function:
// TypeFor returns the [Type] that represents the type argument T.
func TypeFor[T any]() Type {
return TypeOf((*T)(nil)).Elem()
}
Pretty simple! The test code was about ten times longer than the actual function itself.
The use of this function is when you want to create a reflect.Type
object for a certain type, like var intType = reflect.TypeFor[int]()
or var errorType = reflect.TypeFor[error]()
.
The proposal to add this to the reflect package was opened by Josh Bleecher Snyder. It marks yet another step in the ongoing march of generics through the standard library.
The hardest part of this proposal was probably coming up with the right name for the function. reflect.TypeOf
would be the most natural name for the function, but it’s already taken by the existing function for creating a reflect.Type
object. Alternatives considered include:
StaticTypeOf
MakeType
TypeOfT
TypeFrom
GetType
AsType
ToType
NewType
TheType
Types
ConstantType
T
Naming things remains one of the two hard problems in computer science.
I tossed out the winning name, but at that point we were just going through a list of all possible adjectives and prepositions, so someone was going to hit on TypeFor
eventually.
I am, in general, opposed to the “useless use of generics” in which a generic function is used even when it doesn’t add any type safety and has no or minimal length savings. For example, making a generic version of json.Marshal
would be a useless use of generics because it doesn’t actually add any type safety. However, in this case, the right way to build a reflect.Type
for an interface is fairly obscure, so I think it was worth adding.
The issue goes back to the First Law of Reflection in Go: “Reflection goes from interface value to reflection object.” When you call reflect.TypeOf(0)
, you get what you expect: a reflect.Type
object for the type int
. But if you call reflect.TypeOf(err)
you may be surprised by what you get. Instead of getting an object containing the type error
, you get an object with the underlying concrete type of err
. That is because error
is an interface value, and when you call a function in Go, interface parameters of functions are converted as needed, so the error interface is lost and only the concrete value is visible to reflect.TypeOf
. Worse, if err
is nil, you get a nil back from reflect.TypeOf
.
Chris Siebenmann wrote a good explanation of what’s going on and how to work around it:
Because
reflect.TypeOf()
is passed an ‘interface{}
’ (these days also known as ‘any
’), it can’t directly give you the reflect.Type of an interface, because that interface type will be lost when it’s converted to ‘interface{}
’. Insteadreflect.TypeOf()
returns the underlying non-interface type (or nil). As we’ve seen in the context of ‘nil
’ being only sort of typed, to get around this you must pass a pointer to the interface (well, a value of the interface), get back the type ‘pointer to’, and then dereference to the original type through reflect again…
Before Go 1.22, the solution if you wanted to create reflect.Type
for an interface was to call reflect.TypeOf((*TheInterface)(nil)).Elem()
. This worked, but it was not very intuitive. I know I struggled to figure it out in my own code that uses the reflect package.
When reflect.TypeFor
was proposed, there was also an alternate proposal which would have added a generic version of the reflect.ValueOf function too. I argued in the issue comments that this would be a mistake:
A cursory look at just the standard library reveals dozens of places where
reflect.T[whatever]()
could be substituted in for greater clarity.reflect.GenericValueOf
is harder to qualify because there are a few places where the interface type was clearly what was wanted, but in a lot of cases, the underlying concrete type is the target. Addingreflect.GenericValueOf
will add a level of ambiguity when reading code: Does the author really want the interface type here or did they just see generics and assume this function is newer and therefore better? It adds a second way of doing things without being 100% clear about what’s intended. So I’m leaning towards +1 onreflect.T
but +0 onreflect.GenericValueOf
.
Sometimes the best thing about the Go standard library is all the things that get left out. reflect.GenericValueOf
would have crossed the fuzzy line into “useless use of generics” in my opinion.
So that’s the story of reflect.TypeFor
. There’s a story behind every function in the standard library. See the next entries in this series for the stories behind slices.Concat and cmp.Or.