Interface
golang 的 接口(interface) 是一种特殊的类型,有两种类型 [1]:
- 基本接口(basic interfaces):只包含方法集(method set),这类接口可以用作接口类型变量,被称为基本接口类型(basic interface types)
- 类型约束接口(type constraint interfaces):包含类型集合(type set),这是 go 1.18 添加泛型引入的,只能作为泛型约束条件,不能作为接口类型变量使用
接口类型可能由以下两类元素组成:
- 方法元素(method element):定义了方法
- 类型元素(type element):可以是类型名(type name)、类型字面量(type literal)、近似类型(approximation type)或类型集合(type union)
其中类型元素中的类型名和类型字面量其实也是类型要求的一种,因为 golang 编译器会将嵌套方法铺平。
type error interface {
Error() string
}
// 方法元素+类型元素的类型名、类型字面量
type ReadWriteCloser = interface {
Read(buf []byte) (n int, err error)
Write(buf []byte) (n int, err error)
error // a type name
interface{ Close() error } // a type literal
}
// 等同于
interface {
Read(buf []byte) (n int, err error)
Write(buf []byte) (n int, err error)
Error() string
Close() error
}
基本接口
基本接口类型在 golang 运行时中其实是两类结构体:
iface
:拥有方法的接口类型变量eface
:没有方法的空接口类型变量
每个接口类型变量都由两个指针组成。
iface
结构体
type iface struct {
tab *itab
data unsafe.Pointer
}
data
指针指向具体类型变量的值tab
指针存储类型信息- 接口信息(Inter):接口类型、方法集等
- 具体类型信息(Type)
- 类型哈希(Hash):类型转换时用于判断类型是否匹配
- 虚函数表(Fun):调用接口方法时动态分发
// The first word of every non-empty interface type contains an *ITab.
// It records the underlying concrete type (Type), the interface type it
// is implementing (Inter), and some ancillary information.
//
// allocated in non-garbage-collected memory
type ITab struct {
Inter *InterfaceType
Type *Type
Hash uint32 // copy of Type.Hash. Used for type switches.
Fun [1]uintptr // variable sized. fun[0]==0 means Type does not implement Inter.
}
type InterfaceType struct {
Type
PkgPath Name // import path
Methods []Imethod // sorted by hash
}
// Type is the runtime representation of a Go type.
//
// Be careful about accessing this type at build time, as the version
// of this type in the compiler/linker may not have the same layout
// as the version in the target binary, due to pointer width
// differences and any experiments. Use cmd/compile/internal/rttype
// or the functions in compiletype.go to access this type instead.
// (TODO: this admonition applies to every type in this package.
// Put it in some shared location?)
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 // enumeration for C
// 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
}
eface
结构体
type eface struct {
_type *_type
data unsafe.Pointer
}
type _type = abi.Type
data
指针指向具体类型变量的值_type
指针存储具体类型信息
接口的值装箱
前文提到接口变量都是通过指针指向实际的值,当实际的值不在堆上,go 就会将它浅拷贝一份到堆上然后再指向该新值,这就是值装箱(value boxing)。这会造成一定的性能损失。[2]
参考 代码:主动调用 mallocgc 复制。
// convT converts a value of type t, which is pointed to by v, to a pointer that can
// be used as the second word of an interface value.
func convT(t *_type, v unsafe.Pointer) unsafe.Pointer {
if raceenabled {
raceReadObjectPC(t, v, sys.GetCallerPC(), abi.FuncPCABIInternal(convT))
}
if msanenabled {
msanread(v, t.Size_)
}
if asanenabled {
asanread(v, t.Size_)
}
x := mallocgc(t.Size_, t, true)
typedmemmove(t, x, v)
return x
}