preface
This article will mainly introduce the interface {} in golang and unlock its mysterious veil. Before introducing it, we need to understand the type system of golang, and then introduce the use of interface, the underlying principle of interface and the principle of interface in reflection
Type system
The built-in types of Golang include int8 int16 int32 int64 int float byte string slice map chan func, etc. of course, we can also define custom types, such as
type MyInt int type T struct{ name string } type I interface{ Name() string }
be careful:
- You cannot define a method for a built-in type, but you can define a method for a user-defined type MyInt, which needs to be different from type MyInt2= int. here MyInt2 is an alias of int, which is essentially the same type. Although the underlying type of MyInt is int, it belongs to a new user-defined type
Interface type is an invalid method recipient. as
func (i I)foo()// The compiler will report an error
Both built-in type and user-defined type information have type metadata, and each type metadata has a globally unique type description, which is somewhat similar to the Class information in Java.
So what does type metadata look like?
//$GOROOT/src/runtime.type.go type _type struct { size uintptr ptrdata uintptr // size of memory prefix holding all pointers hash uint32 tflag tflag align uint8 fieldAlign uint8 kind uint8 // 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. // If the KindGCProg bit is set in kind, gcdata is a GC program. // Otherwise it is a ptrmask bitmap. See mbitmap.go for details. gcdata *byte str nameOff ptrToThis typeOff } func (t *_type) uncommon() *uncommontype { if t.tflag&tflagUncommon == 0 { return nil } switch t.kind & kindMask { case kindStruct: type u struct { structtype u uncommontype } return &(*u)(unsafe.Pointer(t)).u ...//For the convenience of explanation, some other types are omitted here case kindSlice: type u struct { slicetype u uncommontype } return &(*u)(unsafe.Pointer(t)).u } } //Other descriptive information type uncommontype struct { pkgpath nameOff mcount uint16 // number of methods xcount uint16 // number of exported methods moff uint32 // offset from this uncommontype to [mcount]method _ uint32 // unused } //Built in slice type type slicetype struct { typ _type elem *_type //The type pointer of the element stored in the slice, such as [] int, then elem points to the pointer inttype of the type metadata of int }
There are also some other description information uncommunontype under each type of meta information, which records the package path, the number of methods, and the offset of storing method metadata data.
For example, the above MyInt defines some methods as follows
type MyInt struct func(m MyInt)Hello(){ fmt.Println("hello") }
Then the type metadata of MyInt is
<img src="https://tva1.sinaimg.cn/large/8dfd1ceegy1h028vkuvpoj20qk0hiac3.jpg" alt="image" style="zoom:50%;" />
Interface
duck typing is a dynamic style in programming. Generally speaking,
When you see a bird walking like a duck, swimming like a duck and barking like a duck, then the bird can be called a duck.
That is, the focus is on the behavior of the object, not the object itself.
golang uses "structural typing" similar to "duck typing", except that it occurs at the compilation stage.
type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } type File struct { *file // os specific } func (f *File) Read(b []byte) (n int, err error) { if err := f.checkValid("read"); err != nil { return 0, err } n, e := f.read(b) return n, f.wrapErr("read", e) } func (f *File) Write(b []byte) (n int, err error) { if err := f.checkValid("write"); err != nil { return 0, err } n, e := f.write(b) if n < 0 { n = 0 } if n != len(b) { err = io.ErrShortWrite } epipecheck(f, e) if e != nil { err = f.wrapErr("write", e) } return n, err }
For example, the Reader and Writer interfaces defined in the io package and the File structure in the os package implement these two interfaces. In fact, even if File implements this interface, it does not need to display the corresponding interface of implementation to Java and other languages.
Interface infrastructure
Interfaces mainly include empty interfaces interface {} and non empty interfaces (such as Reader and Writer mentioned above). Let's see how the underlying data interfaces of empty interfaces and non empty interfaces are represented
interface{}
//$GOROOT/src/runtime/runtime2.go type eface struct { _type *_type data unsafe.Pointer }
Among them_ Type refers to the metadata of dynamic type, and data refers to the value of dynamic type, such as
func main() { var ifc interface{} f, _ := os.Open("main.go") ifc = f fmt.Println(ifc) }
ifc before assignment_ Both type and data are nil, and f is * OS File, then after assignment_ Type points to * OS The type metadata of file (which contains the Filed information of the structure and the Method array of methods), and the data points to f
Non empty interface
//$GOROOT/src/runtime/runtime2.go type iface struct { tab *itab data unsafe.Pointer } type itab struct { inter *interfacetype //Interface Metadata _type *_type //Dynamic type hash uint32 // copy of _type.hash. Used for type switches. _ [4]byte fun [1]uintptr //Although only the data with size 1 is defined at runtime, it stores the pointer to the first address of the function. When there are multiple functions, the pointer will be stored under the data in turn, which can be found through the first address + offset } type interfacetype struct { typ _type //Metadata of interface pkgpath name //Package name mhdr []imethod//List of interface defined methods }
Like the empty interface, data points to the actual dynamic value. itab is the core of the interface, which records the interface type metadata inter and dynamic types_ Type, where fun is the inter
The interface method defined in the interface type metadata is in the actual dynamic type_ Type.
The above figure is quoted from [[Yulin laboratory] Golang interface]( https://www.bilibili.com/vide...)
It should be noted that when the interface type and dynamic type are determined, the itab will be fixed, so golang will cache the itab used, and store it in runtime with the interface type and dynamic type as the key and the itab pointer as the value Itabtabletype this hash table
//$GOROOT/src/runtime/iface.go type itabTableType struct { size uintptr // length of entries array. Always a power of 2. count uintptr // current number of filled entries. entries [itabInitSize]*itab // really [size] large } func itabHashFunc(inter *interfacetype, typ *_type) uintptr { // compiler has provided some good hash codes for us. return uintptr(inter.typ.hash ^ typ.hash) }
The hashtype is different from the hashtype in the hashibah interface. Note that the hashtype in the hashibah interface is used to solve the conflict between the hashtype and the hashtype in the hashibah interface. If there is no corresponding key/value in the hash table, it is created and added to the table.
Type Asserts
In business coding, we need to carry out type conversion, which requires the use of interface assertion
var ifc interface{} f, _ := os.Open("main.go") ifc = f reader,ok :=ifc.(io.Reader) //Type assertion method I switch ifc.(type) { //Type assertion method 2 case io.Reader: reader := ifc.(io.Reader) default: fmt.Println("assert type fail") }
The above are two kinds of method assertions, so type assertions can be divided into the following four cases
- Empty interface (specific type)
- Non empty interface (specific type)
- Empty interface (non empty interface)
- Non empty interface (non empty interface)
Empty interface (specific type)
We only need to judge the value of the empty interface eface_ Whether the type is consistent with the dynamic type, such as f, ok: = ifc (* os.File), the bottom layer of ifc is * OS File, so ok is true
Non empty interface (specific type)
According to the runtime we mentioned earlier Itabtabletype stores the cache of itab. When asserting the type, we only need to take the non empty interface and specific type as the key to look up the itab in the hash table. If the itab pointer found is consistent with the itab in iface, the type assertion is successful
Empty interface (non empty interface)
We know that eface includes dynamic to types_ Type, then we can use this non empty interface and dynamic type as the key to run time Look up itab in itabtabletype cache. If it is found, it indicates that the empty interface type assertion is successful. If it is not found, look up whether the method list of the uncommunontype of the dynamic type implements all the methods mhdr defined in the non empty interface interfacetype, and assemble the results into a new itab and insert it into runtime Itabtabletype. The next type assertion can be judged quickly.
It should be noted here that if the dynamic type of the empty interface_ Type does not implement the method in the interface type of the empty interface, and will also be assembled into an itab and added to the cache, except that the fun[0]=0 of the itab
For example, f, OK: = ifc (io.ReaderWriter) will first use the dynamic type * OS. Of ifc File and Io The readerwriter is the key, which is displayed at runtime Find itab in itabtabletype. If found, func [0]= 0, the type assertion succeeds. If func[0]==0, the type assertion fails. If not, compare * OS Whether the uncommunontype of file implements io The interface of readerwriter defines the method and assembles itab to add it to the hash table
Non empty interface (non empty interface)
The method is similar to the above. We will explain it directly with examples
var r io.Reader f, _ := os.Open("main.go") r = f rw,ok := r.(io.ReadWriter)
We can get from r or from inter_ Dynamic type of type * OS File, and then set io Readwriter and * OS File is the key, which is displayed at runtime Find itab in itabtabletype, and the subsequent steps are the same as the empty interface (non empty interface) the process of type conversion is not described in detail.
reflex
The metadata of the type we already know above is defined in the runtime package and is not exported. In order to obtain the write type data at runtime and make reflection calls on the line, the reflection package defines a set of the same exported type structure, as shown in the following figure
1. interface {} to reflect Type
//$GOROOT/src/reflect/type.go func TypeOf(i interface{}) Type { eface := *(*emptyInterface)(unsafe.Pointer(&i)) return toType(eface.typ) }
The TypeOf method converts an empty interface into a specific Type. The Type is an interface and provides many methods to obtain metadata
//$GOROOT/src/reflect/value.go type Type interface { Method(int) Method MethodByName(string) (Method, bool) NumMethod() int Name() string PkgPath() string Kind() Kind Implements(u Type) bool AssignableTo(u Type) bool Comparable() bool ... common() *rtype uncommon() *uncommonType }
2. Through reflect Value modify value
type Value struct { // typ holds the type of the value represented by a Value. typ *rtype // Pointer-valued data or, if flagIndir is set, pointer to data. // Valid when either flagIndir is set or typ.pointers() is true. ptr unsafe.Pointer // flag holds metadata about the value. // The lowest bits are flag bits: // - flagStickyRO: obtained via unexported not embedded field, so read-only // - flagEmbedRO: obtained via unexported embedded field, so read-only // - flagIndir: val holds a pointer to the data // - flagAddr: v.CanAddr is true (implies flagIndir) // - flagMethod: v is a method value. // The next five bits give the Kind of the value. // This repeats typ.Kind() except for method values. // The remaining 23+ bits give a method number for method values. // If flag.kind() != Func, code can assume that flagMethod is unset. // If ifaceIndir(typ), code can assume that flagIndir is set. flag // A method value represents a curried method invocation // like r.Read for some receiver r. The typ+val+flag bits describe // the receiver r, but the flag's Kind bits say Func (methods are // functions), and the top bits of the flag give the method number // in r's type's method table. }
The above is the reflection structure of Value, which can be used to obtain or modify the content it points to (of course, the modification requires that the Value is a pointer type)
// ValueOf returns a new Value initialized to the concrete value // stored in the interface i. ValueOf(nil) returns the zero Value. func ValueOf(i interface{}) Value { if i == nil { return Value{} } // TODO: Maybe allow contents of a Value to live on the stack. // For now we make the contents always escape to the heap. It // makes life easier in a few places (see chanrecv/mapassign // comment below). escapes(i) return unpackEface(i) }
ValueOf, similar to TypeOf, provides a conversion method from interface {} to reflection value.
Next, let's look at how to modify the value through it
var x float64 = 3.4 v := reflect.ValueOf(x) v.SetFloat(7.1) // Error: will panic.
The above cannot be modified because x is a value type and v.SetFloat modifies the content of the value copy of X, which is meaningless. Therefore, golang does not allow this scene to appear and will panic
How do we need to modify it? You can pass in its pointer as follows
var x float64 = 3.4 p := reflect.ValueOf(&x) // Note: take the address of x. fmt.Println("type of p:", p.Type()) //type of p: *float64 fmt.Println("settability of p:", p.CanSet())//settability of p: false, the pointer cannot be modified, only the content pointed to by the pointer can be modified v := p.Elem() fmt.Println("settability of v:", v.CanSet())//settability of v: true v.SetFloat(7.1) fmt.Println(v.Interface()) //7.1 fmt.Println(x)//7.1
Similarly, a structure can modify its value by passing a pointer
type T struct { A int B string } t := T{23, "skidoo"} s := reflect.ValueOf(&t).Elem() typeOfT := s.Type() for i := 0; i < s.NumField(); i++ { f := s.Field(i) fmt.Printf("%d: %s %s = %v\n", i, typeOfT.Field(i).Name, f.Type(), f.Interface()) } //0: A int = 23 //1: B string = skidoo s.Field(0).SetInt(77) s.Field(1).SetString("Sunset Strip") fmt.Println("t is now", t)//t is now {77 Sunset Strip}
3. reflect.Value conversion interface {}
When we get reflect through reflection After value, we often need to convert it to its original type for use. This is that we need to convert it into interface {}, and then convert it to a specific type through type
// Interface returns v's value as an interface{}. func (v Value) Interface() interface{}
for example
v=reflect.ValueOf(3.4) y := v.Interface().(float64) // y will have type float64. fmt.Println(y)
summary
This paper introduces the type system of golang, and the interface at the bottom, including empty interface eface and non empty interface iface. It has its data interface at the bottom, and introduces the bottom mechanism of type conversion and reflect in reflection Type and reflect The relationship between value and empty interface eface and non empty interface iface, and how to modify the value of the underlying dynamic type through reflection