TL;DR: _type is the runtime representation of a Go type. It contains metadata about the type and it’s
located in the read-only data segment of the compiled binary. Every concrete type has exactly one _type and this info
can be accessed by all goroutines.
In Go, every variable has a type, but how does Go represent types internally? In this post, we will
explore the internal structure of _type in Go’s runtime.
Overview of _type
_type is the runtime representation of a Go type. It is defined in the runtime package,
In the runtime/type.go file, we can see the
below code snippet:
type _type = abi.Type
This means that _type is an alias for abi.Type. So let’s take a look at the Type struct in
the internal/abi package.
type Type struct {
Size_ uintptr
PtrBytes uintptr // number of (prefix) bytes in the type that can contain pointers
Hash uint32 // hash of type; avoids computation in hash tables
TFlag TFlag // extra type information flags
Align_ uint8 // alignment of variable with this type
FieldAlign_ uint8 // alignment of struct field with this type
Kind_ Kind // what kind of type this is (string, int, ...)
// function for comparing objects of this type
// (ptr to object A, ptr to object B) -> ==?
Equal func(unsafe.Pointer, unsafe.Pointer) bool
// GCData stores the GC type data for the garbage collector.
// Normally, GCData points to a bitmask that describes the
// ptr/nonptr fields of the type. The bitmask will have at
// least PtrBytes/ptrSize bits.
// If the TFlagGCMaskOnDemand bit is set, GCData is instead a
// **byte and the pointer to the bitmask is one dereference away.
// The runtime will build the bitmask if needed.
// (See runtime/type.go:getGCMask.)
// Note: multiple types may have the same value of GCData,
// including when TFlagGCMaskOnDemand is set. The types will, of course,
// have the same pointer layout (but not necessarily the same size).
GCData *byte
Str NameOff // string form
PtrToThis TypeOff // type for pointer to this type, may be zero
}
As we can see, the _type struct contains various fields that provide information about the type,
such as its size, alignment, kind, and methods for comparison and garbage collection.
Based on that, Go defines other type structures that embed _type to represent more specific types, like ArrayType,
ChanType, FuncType, InterfaceType, MapType, PtrType, SliceType, StructType, etc.
For example:
type InterfaceType struct {
Type
PkgPath Name // import path
Methods []Imethod // sorted by hash
}
Kind
A Kind represents the specific kind of type that a Type represents.
// The zero Kind is not a valid kind.
type Kind uint8
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Pointer
Slice
String
Struct
UnsafePointer
)
So in Go, Every concrete type has exactly one _type object created by the compiler,
which means
if multiple variables have the same type, they will point to the same _type object.
Where _type lives
The _type objects are stored in the read-only data segment of the compiled binary. This guarantees that
- They are immutable
- Shared across all goroutines
- Available as soon as the program starts - no runtime allocation needed.
Where _type is used
There are several places where _type is used in Go’s runtime:
Interface assignments
var x int = 42
var y interface{} = x // y's _type points to int's _type
when we write the code like above, the interface variable y will hold a pointer to the _type of int, just like
below:
y._type = &_type.int
y.data = &x
Without the _type information, the runtime wouldn’t know how to interpret the data stored in the interface.
Reflection
When you use the reflect package to inspect types at runtime, it relies heavily on _type information.
t := reflect.TypeOf(x)
Under the hood, reflect.TypeOf just returns a wrapper around _type. That’s why reflect.Type can tell you size,
kind,
fields, methods. it’s all in _type.
Type switches / assertions
switch v := y.(type) {
case int:...
case string:...
}
At runtime, Go compares y._type against the _type of each case to find a match.
Other places
_type is also used in various other parts of the runtime, but the above are the most common scenarios.