2019-05-16
Go has no type for types in the language
Over on r/golang, someone asked What's the point of limiting .(type) evaluations to type switches:
I know the Go feature set is very well thought out and I'm sure there's a good reason for it, but I'm curious why I can't do
fmt.Println(x.(type))
(As pointed out in the replies, you can get the same information
with fmt's %T
formatting verb.)
Although there are a number of things going on here, one of them
is that Go has opted not to make types visible as explicit entities
in the language. In a language like Python, the type of things
is explicitly exposed as a fully visible part of the language,
through operations like type()
. In Go, there's no such direct
way of exposing the type of something and you can only compare it
against other types through mechanisms like type switches.
(Python goes further and makes types pretty much as first class entities as functions are.)
Part of what this means is that in Go, you cannot write an expression
like 'x := y.(type)
' not just because the language syntax forbids
it, but because there is no standard type that the variable x
can
be. If you wanted to allow this, you would have to create a new Go
type and define what its behavior was.
Go does make type information accessible, but only through the
reflect
standard library package.
There are two things about this. First, reflect
isn't part of the
language itself, so another implementation of Go would be theoretically
free to leave it out (although a certain amount of code would have
problems); TinyGo doesn't quite go that
far, although it has a minimal version. Second, it's relatively
clear that what you get from reflect
and manipulate through it
is not literally the type information from the language; instead,
it is a reflect
-created thing that may or may not actively reflect
the underlying reality. The only nominal exceptions are
reflect.SliceHeader
and reflect.StringHeader
, and reflect
explicitly says that you can't use these safely.
(My personal guess is that part of why reflect
provides them is
so that if the string or slice header ever changes, code can break
visibly because the reflect
structure fields it's trying to use
aren't there any more. This wouldn't happen if Go forced everyone
to define their own private versions of these runtime structures;
instead you'd just get silently corrupted results. And people do
do things with these headers today.)
In general, not explicitly requiring Go the language to have types as explicit, visible entities preserves some implementation freedom. However, I believe that Go still does require that an implementation keeps around some more type information than you'd expect; for example, I don't believe it would be proper in general for a Go implementation to reduce everything from distinct named types to structural types after compiling the program. This is because if you have the following code, I believe the type cast is required to fail:
type fred int var a fred [...] b := interface{}(a) i, ok := b.(int)
(You can construct a more elaborate example with a type switch with
both fred
and int
as options.)
Structurally, fred
and int
are the same thing, but Go explicitly
distinguishes between named types even if they are structurally
identical. As seen here, this distinction can be recovered dynamically,
which implies that it must be accessible to the runtime; the runtime
needs to have some way to distinguish between a fred
and an int
that have been turned into interfaces.
(You can get similar situations if the two differently named types have different method sets and you are converting between different interfaces; here, one type might be convertible but the other not.)
PS: One reason a Go implementation might be interested in some
degree of type erasure is to reduce the amount of additional type
information that has to be carried around with the compiled binary.
If the actual code never needs the information, why have it present
at all? But clearly this would require whole-program analysis so
you can tell whether or not these things are needed, and you'd
probably have to explicitly not support things like fmt's %T
verb.