漫行记Wandering
Journal 3,378 字 12 分钟 Golang

Golang: _type

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.

← 随笔