NOTE
1.4 interface
Empty and non-empty interfaces, how interfaces are used, type conversion and assertions, iface/eface internals, nil comparison, and interface conversion.
This is a historical learning note and may contain outdated or incomplete understanding.
1. What Kinds There Are
1.1. Empty Interface interface{}
It can receive data of any type. It only needs to record where the data is and what type it is.
1.1.1. Example
var e interface{}
The underlying structure is as follows:
type eface struct {
_type *_type// points to the dynamic type metadata of the interface
data unsafe.Pointer// points to the dynamic value of the interface
}
Assignment:
f, _ := os.Open("eggo.txt")
e = f
1.2. Non-empty Interface
A non-empty interface is an interface type with a method list. For a variable to be assigned to a non-empty interface type, its type must implement all methods required by the interface.
1.2.1. Example
var rw io.ReadWriter
The underlying structure is as follows:

type iface struct {
tab *itab// interface type metadata + dynamic type metadata
data unsafe.Pointer// dynamic value of the interface
}
type itab struct {
inter *interfacetype// interface type metadata
_type *_type// dynamic type metadata
hash uint32
_ [4]byte
fun [1]uintptr
}
type interfacetype struct {
typ _type
pkgpath name
mhdr []imethod
}
Assignment:
f, _ := os.Open("eggo.txt")
rw = f
Declare another variable and assign it:
var w io.Writer = f
-

-
Summary
- A unique
itabstructure can be found through<interface type, dynamic type> - Therefore, Golang uses
<interface type, dynamic type>as the key and theitabstructure as the value, caches them in a hash table, and reuses them
- A unique
2. What Is an interface
A set of method signatures.
3. Why interface Is Needed
Decouple upstream and downstream. The upstream only needs to know the downstream interface and does not need to know the details of the downstream implementation.
4. How to Use It
4.1. Implement an Interface
Go does not require explicit interface implementation; it uses duck typing.
4.1.1. Duck Typing
If something looks like a duck, swims like a duck, and quacks like a duck, then it can be regarded as a duck. In other words, as long as something implements A’s interface, it can be regarded as an A. It focuses more on how an object is used than on the object’s type itself.
type People interface {
Run()
}
type Student struct {
}
func (s *Student) Run() {
fmt.Println("running...")
}
func TestInterface1(t *testing.T) {
var p People = &Student{}
p.Run()
}
// output
running...
4.2. Polymorphism
Polymorphism is runtime behavior that allows one type to have the capabilities of multiple types.
- Interface
// interface
type Phone interface {
Call()
}
// polymorphic function
func Call(phone Phone) {
phone.Call()
}
- Implementations
// implementation 1
type Nokia struct {
//Phone// it is fine whether the implemented interface is written explicitly or not
}
func (n *Nokia) Call() {
fmt.Println("I am Nokia.")
}
// implementation 2
type Redmi struct {
}
func (r Redmi) Call() {
fmt.Println("I am Redmi.")
}
- Test
func TestNokia_Call(t *testing.T) {
Call(&Nokia{})
Call(&Redmi{})
}
4.3. Inheritance
By embedding an anonymous type member in a struct, the properties and methods of the anonymous type can be inherited.
4.3.1. Inheriting Properties
type Point struct{ X, Y float64 }
type ColoredPoint struct {
Point // inherit the anonymous struct
}
func TestInterface1(t *testing.T) {
var cp ColoredPoint
cp.X = 1
cp.Point.Y = 2
fmt.Println(cp.Point.X) // "1"
fmt.Println(cp.Y) // "2"
}
// output
1
2
4.3.2. Inheriting Methods
type Cache struct {
m map[string]string
sync.Mutex
}
func (c *Cache) Lookup(key string) string {
c.Lock() // the compiler automatically expands p.Mutex.Lock()
defer c.Unlock() // the compiler automatically expands p.Mutex.Unlock()
return c.m[key]
}
func TestInterface2(t *testing.T) {
c := &Cache{}
value := c.Lookup("test")
fmt.Println(value)
}
4.4. Interface Conversion
4.4.1. Type Conversion
:= ( )
type MyInt int
func TestType1(t *testing.T) {
var a MyInt = 1
var b int = 1
//fmt.Println(a == b)// different types cannot be compared
fmt.Println(a == MyInt(b))
}
// output
true
4.4.2. Type Assertion
| A.(B) | Empty interface | Non-empty interface | Check |
|---|---|---|---|
| Concrete type | empty interface.(concrete type) | non-empty interface.(concrete type) | Whether A’s dynamic type is B |
| Non-empty interface | empty interface.(non-empty interface) | non-empty interface.(non-empty interface) | Whether A’s dynamic type implements the methods of B’s dynamic type |
4.4.2.1. Empty interface.(concrete type)
- Assertion succeeds
var e interface{}
f,_ := os.Open("eggo.txt")
e = f
r,ok := e.(*os.File)
- Assertion fails
var e interface{}
f := "eggo"
e = f
r,ok := e.(*os.File)
4.4.2.2. Non-empty interface.(concrete type)
Check whether iface.tab equals the itab corresponding to <interface type, dynamic type>.
- Assertion succeeds
var rw io.ReadWriter
f,_ := os.Open("eggo.txt)
rw = f
r,ok := rw.(*os.File)
4.4.2.3. Empty interface.(non-empty interface)
- Assertion succeeds
var e interface{}
f,_ := os.Open("eggo.txt")
e = f
rw,ok := e.(io.ReadWriter)
4.4.2.4. Non-empty interface.(non-empty interface)
- Assertion succeeds
var w io.Writer
f,_ := os.Open("eggo.txt")
w = f
rw,ok := w.(io.ReadWriter)
Safe type assertion:
, := .(target type)
Unsafe type assertion::= .(target type)
func TestType2(t *testing.T) {
var a interface{}
var b int = 1
a = b
c := a.(int)// type assertion
fmt.Println(c)
}
// output
1
4.4.2.5. Static Type, Dynamic Type, and Dynamic Value
func TestInterface3(t *testing.T) {
var reader io.Reader // reader's static type is io.Reader, dynamic type is nil, and dynamic value is nil
file, err := os.OpenFile("test", os.O_RDWR, 0)
if err != nil {
panic(err)
}
reader = file // reader's static type is io.Reader, dynamic type is *os.File, and dynamic value is file
writer := reader.(io.Writer) // because file implements io.Writer, it can be asserted to Writer here
// writer's static type is io.Writer, dynamic type is *os.File, and dynamic value is file
var empty interface{}
empty = writer // no assertion is needed because all interfaces implement the empty interface
fmt.Println(reader, file, writer, empty)
}
// output
&{0xc000074a00} &{0xc000074a00} &{0xc000074a00} &{0xc000074a00}
4.4.3. Type Conversion vs Type Assertion
| Type Conversion | Type Assertion | |
|---|---|---|
| Same | Converts one type into another type | Converts one type into another type |
| Difference | Operates on ordinary variables | Operates on interface variables |
5. Implementation
5.1. iface and eface
| iface | eface | |
|---|---|---|
| Same | Both are used to describe interfaces | Both are used to describe interfaces |
| Difference | Interface with methods | Empty interface interface{} |
5.1.1. iface
type iface struct {
tab *itab// points to itab. Mainly contains the interface type and concrete type
data unsafe.Pointer// points to the concrete value
}
type itab struct {
inter *interfacetype// describes the interface type
_type *_type// describes the concrete type, including memory alignment, size, etc.
link *itab
hash uint32 // copy of _type.hash. Used for type switches.
bad bool // type does not implement interface
inhash bool // has this itab been added to hash?
unused [2]byte
fun [1]uintptr // addresses of methods of the concrete data type corresponding to interface methods, implementing dynamic dispatch for interface method calls
}
type interfacetype struct {
typ _type// structure describing various data types in Go
pkgpath name// defines the package name of the interface
mhdr []imethod// function list defined by the interface
}
type _type struct {
// type size
size uintptr
ptrdata uintptr
// hash value of the type
hash uint32
// type flag, related to reflection
tflag tflag
// memory alignment
align uint8
fieldalign uint8
// type number, such as bool, slice, struct, etc.
kind uint8
alg *typeAlg
// related to gc
gcdata *byte
str nameOff
ptrToThis typeOff
}
// Various data types add extra fields on top of _type for management
type arraytype struct {
typ _type
elem *_type
slice *_type
len uintptr
}
type chantype struct {
typ _type
elem *_type
dir uintptr
}
type slicetype struct {
typ _type
elem *_type
}
type structtype struct {
typ _type
pkgPath name
fields []structfield
}

5.1.2. eface
type eface struct {
_type *_type// describes the concrete type, including memory alignment, size, etc.
data unsafe.Pointer// points to the concrete value
}

5.2. Comparing interface and nil
First, the value of an interface is tab (pointing to type information) + data (pointing to the concrete data).
Then, the zero value of an interface has both tab and data equal to nil.
Finally, the zero value of an interface is equal to nil.
type Coder interface {
code()
}
type Gopher struct {
name string
}
func (g Gopher) code() {
fmt.Printf("%s is coding\n", g.name)
}
func getGopher() Coder {
var g *Gopher
fmt.Printf("g==nil: %v, Type: %T, Data: %v\n", g == nil, g, g)
return g
}
func TestInterface1(t *testing.T) {
var c Coder
fmt.Printf("c==nil: %v, Type: %T, Data: %v\n", c == nil, c, c)
c = getGopher()
fmt.Printf("c==nil: %v, Type: %T, Data: %v\n", c == nil, c, c)
}
// output
c==nil: true, Type: <nil>, Data: <nil>
g==nil: true, Type: *interface1.Gopher, Data: <nil>
c==nil: false, Type: *interface1.Gopher, Data: <nil>
5.3. How Go Determines Whether a Type Implements an Interface
An itab consists of an interface type and a concrete type.
Go matches the type’s method set against the method set required by the interface. If the type’s method set completely contains the interface’s method set, the type can be considered to implement the interface.
For example, if a type has m methods and an interface has n methods, it is easy to see that the time complexity of this determination is O(mn). Go sorts the functions in the method sets lexicographically by function name, so the actual time complexity is O(m+n).
5.4. How Interface Conversion Works
// inter represents the interface type, i represents an interface bound to a concrete type, and r represents the new iface after interface conversion
func convI2I(inter *interfacetype, i iface) (r iface) {
tab := i.tab
if tab == nil {
return
}
if tab.inter == inter {
r.tab = tab
r.data = i.data
return
}
r.tab = getitab(inter, tab._type, false)
r.data = i.data
return
}
func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
// ……
// Calculate a hash value from inter and typ
h := itabhash(inter, typ)
// look twice - once without lock, once with.
// common case will be no lock contention.
var m *itab
var locked int
for locked = 0; locked < 2; locked++ {
if locked != 0 {
lock(&ifaceLock)
}
// Traverse one slot in the hash table
for m = (*itab)(atomic.Loadp(unsafe.Pointer(&hash[h]))); m != nil; m = m.link {
// If the itab has already been found in the hash table (both inter and typ pointers are the same)
if m.inter == inter && m._type == typ {
// ……
if locked != 0 {
unlock(&ifaceLock)
}
return m
}
}
}
// No itab was found in the hash table, so create a new itab
m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))
m.inter = inter
m._type = typ
// Add it to the global hash table
additab(m, true, canfail)
unlock(&ifaceLock)
if m.bad {
return nil
}
return m
}

Discussion
Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub