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.

GoCreated Updated 3 min readhistorical

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 itab structure can be found through <interface type, dynamic type>
    • Therefore, Golang uses <interface type, dynamic type> as the key and the itab structure as the value, caches them in a hash table, and reuses them

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
}

6. References

Discussion

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