Search CTRL + K

Interface

golang 的 接口(interface) 是一种特殊的类型,有两种类型 [1]

接口类型可能由以下两类元素组成:

其中类型元素中的类型名和类型字面量其实也是类型要求的一种,因为 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 结构体

type iface struct {
	tab  *itab
	data unsafe.Pointer
}
// 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

接口的值装箱

前文提到接口变量都是通过指针指向实际的值,当实际的值不在堆上,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
}

  1. https://go101.org/article/interface.html ↩︎

  2. https://goperf.dev/01-common-patterns/interface-boxing/ ↩︎