2024-01-25
In Go, I'm going to avoid using 'any
' as an actual type
As modern Go programmers know, when Go introduced generics it also
introduced a new 'any
' type. This is officially documented as:
For convenience, the predeclared type
any
is an alias for the empty interface.
The 'any' type (alias) exists because it's extremely common in code that's specifying generic types to want to be able to say 'any type', and the way this is done in generics is 'interface{}', the empty interface. This makes generic code clearly easier to read and follow. Consider these two versions of the signature of reflect.TypeFor
func TypeFor[T any]() Type func TypeFor[T interface{}]() Type
These are semantically equivalent but the first is clearer, because you don't have to remember this special case of what 'interface{}' means. Instead, it's right in the name 'any' (and there's less syntactic noise).
But after Go generics became a thing, there's been a trend of
using this new 'any
' alias outside of generic types, instead of
writing out 'interface{}'. I don't think this is a good idea.
To show why, consider the following two function signatures,
both of which use 'any':
func One[T any](v T) boolfunc Two(v any) bool
These two function signatures look almost the same, but they have
wildly different meanings, even if (or when) they're invoked with
the same argument. The effects of 'One(10)
' are rather different
from 'Two(10)
', since 'One' is a generic function while 'Two' is
a regular one. Now consider them written this way:
func One[T any](v T) boolfunc Two(v interface{}) bool
Now we see clearly what Two()
is doing differently than One()
;
it's obvious that it isn't taking 'any type' as such, but instead
it's taking a generic interface as the argument type. This makes
it obvious that a non-interface value will be converted to an
interface value (and will tell some people that an interface value
will lose its interface type).
This increased immediate clarity without needing to remember what
'any
' is why I'm planning to use 'interface{}' in my code in
the future, and why I think you should too. Yes, 'any
' is shorter
and it has a well defined meaning in the specification and we can
probably remember the special meaning all of the time. But why give
ourselves that extra cognitive burden when we can be explicit?
(In generics, the argument goes the other way; 'any' really does mean 'any type', and the 'any' name is clearer than writing 'interface{}' and then needing to remember that that's how generics do it.)
In a sense the 'any' name is a misnomer when used as a type. It's true that 'interface{}' will accept any type, but used as a type, it doesn't mean 'any type'; it means specifically the type 'an empty interface', which is to say an interface that has no methods, which implies interface type conversion (unless you already have an 'interface{}' value). Since 'any' does mean 'any type' in the context of generics, I think it's better to use a different name for each thing, even if Go formally makes the names equivalent. The names of things are fundamentally for people.