The implementation of the Go reflection is closely related to interface and unbroadening.Pointer. If you’re not already familiar with Golang’s underlying implementation of interface, check out my previous article: The Interface underlying implementation of the Go language, unsafe.Pointer will be covered in a future article. (The current Go environment used in this article is Go 1.12.9)
Interface to review
First of all, let’s briefly review the structure of interface. In general, it is:
It is subdivided into iface with functions and eface without functions (interface{}).
There is no functioneface
The function ofiface
The static type(Static interface type) andDynamic mixed type(Dynamic Concrete Type)
In Go, each variable has a unique static type that can be determined at compile time. Some variables may have dynamic mixed types in addition to static types.
For example:
// Interface with function
var r io.Reader
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
iferr ! =nil {
return nil, err
}
r = tty
// Interface with no function
var empty interface{}
empty = tty
Copy the code
The function ofiface
An example of
Var r IO.Reader
Lines 4 through 7 simply assign and get an instance of * os.file, but forget about it. R = tty
There is no functioneface
An example of
Var empty interface{}
And finally empty equals tty
But remember: although there are dynamic mixed types, the external “representation” is still static.
Introduction to Go Reflection
There are three laws of Go reflection:
Reflection goes from interface value to Reflection object. // Reflection object ===> Interface data. 2 From Reflection Object to interface Value. 3. To modify a reflection object, the value must be settable.Copy the code
The reflection of Go is the implementation of these three laws.
The reflection of Go consists of two parts: Type and Value. Type and Value are two structures.
Type:
type Type interface {
Align() int
FieldAlign() int
Method(int) Method
MethodByName(string) (Method, bool)
NumMethod() int
Name() string
PkgPath() string
Size() uintptr
String() string
Kind() Kind
Implements(u Type) bool
AssignableTo(u Type) bool
ConvertibleTo(u Type) bool
Comparable() bool
Bits() int
ChanDir() ChanDir
IsVariadic() bool
Elem() Type
Field(i int) StructField
FieldByIndex(index []int) StructField
FieldByName(name string) (StructField, bool)
FieldByNameFunc(match func(string) bool) (StructField, bool)
In(i int) Type
Key(a) Type
Len(a) int
NumField(a) int
NumIn(a) int
NumOut(a) int
Out(i int) Type
common(a) *rtype
uncommon(a) *uncommonType
}
Copy the code
Value:
type Value struct {
typ *rtype
ptr unsafe.Pointer
flag
}
Copy the code
You will notice that the reflection implementation is similar to the composition of an interface, consisting of “type” and “data value”, but it is worth noting that the “type” and “data value” of the interface are “together”, while the “type” and “data value” of the reflection are separate.
Type and Value provide a variety of methods: getting the list of attributes of an object, getting and modifying the Value of an attribute, the name of the structure to which the object belongs, the underlying Type of the object, and so on
Reflection in Go has two core functions in use:
- reflect.TypeOf(x)
- reflect.ValueOf(x)
These two functions convert a given data object to Type and Value, respectively. Both of these are called reflection objects
Reflection goes from interface value to Reflection Object
Given a data object, the data object can be converted into reflection objects Type and Value.
Example code:
package main
import (
"fmt"
"reflect"
)
func main(a) {
var x float64 = 3.4
t := reflect.TypeOf(x)
v := reflect.ValueOf(x)
fmt.Println("type:", t) //type: float64
fmt.Println("value:", v.String()) //value: <float64 Value>
fmt.Println("type:", v.Type()) // type: float64
fmt.Println("kind is float64:", v.Kind() == reflect.Float64) //kind is float64: true
fmt.Println("value:", v.Float()) / / value: 3.4
}
Copy the code
As you can see from line 17: Value also gets the Type of the current data Value. Therefore, the graph of rule 1 should be:
Reflection goes from Reflection object to interface value.
A given reflection object can be converted to some type of data object. It’s the reverse of rule one.
Notice that you can’t reverse Type, and it makes sense to think about it, what if the invertible Type was converted to? (# ^. ^ #)
Code for rule 1:
package main
import (
"fmt"
"reflect"
)
func main(a) {
var x float64 = 3.4
t := reflect.TypeOf(x)
v := reflect.ValueOf(x)
...
o := v.Interface().(float64) // Code for rule 2
fmt.Println(o)
}
Copy the code
To modify a reflection object, the value must be settable.
Rule three says: by reflecting objects, you can modify the content of the original data.
The reflected object here refers to Value. After all, Type only represents the content related to the Type of the original data, while Value corresponds to the original data object itself.
In all of the examples so far, reflected changes to the Value object do not directly modify the original data object.
package main
import (
"fmt"
"reflect"
)
func main(a) {
var x float64 = 3.4
t := reflect.TypeOf(x)
v := reflect.ValueOf(&x)
....
o := v.Interface().(float64)
fmt.Println(o)
v.SetFloat(5.4) // This line will report an error
fmt.Println(x)
}
Copy the code
This code will declare a panic at 20 lines
reflect: reflect.Value.SetFloat using unaddressable value
Copy the code
The object v is settable. The address is not settable.
We can use the CanSet() method of the Value structure to see if the new Value can be set or modified. The following code tells you that CanSet() returns false.
fmt.Println(v.CanSet()) // false
Copy the code
How do I modify the value of the original data object by reflecting it?
How can I change the value of the original data object by reflecting it or why not?
The reason is simple and pure: In Go, arguments to any function are copies of values, not original data.
The reflect.valueof () function is no exception. The reflection objects we get are the copy of the original object, rather than the original object itself, so we can not modify to the original object; Even if you can modify, modify a reference when the copy, also meaningless, better to report an error. This leads to the settable property of Go reflection from the third law, and extends to methods like CanSet().
So how do you fix it?
First, in order for a function to have “side effects” in Go, a value must be passed as a pointer type.
.var x float64 = 3.4
v := reflect.ValueOf(&x)
...
Copy the code
The value type (*v) of the current type must be obtained. Go provides another method, Elem()
.var x float64 = 3.4
v := reflect.ValueOf(&x)
p := v.Elem()
fmt.Println(p.CanSet()) // true
p.SetFloat(7.1)
fmt.Println(x) / / 7.1
Copy the code
Look at the above code, you can modify the original data.
Reflection principle
It’s not hard to see how similar go’s reflection and interface are in structure! Both are divided into two parts: one is Type and the other is value.
Will reflection be implemented with respect to interface?
What does reflection mean? Reflection means being able to dynamically know the type and structure of a given data object at run time and have the opportunity to modify it! Now a data object, how do you determine what structure it is? Data interface contains structural data ah, as long as you try to get the corresponding memory address of the data, and then convert the data into interface, by checking the type structure of the interface, you can know the structure of the data ah ~ in fact, the above is Go reflection popular principle.
The figure can be shown as:
Unsafe.Pointer the unsafe.Pointer is a Pointer that can point to any data in the Go system.
The source code section (the following section can be ignored, which is a little bit of a pit WHEN I look at the code).
Let’s look at the specific source code: source code in the “GO SDK/ SRC /refelct” package, specific mainly package “type. GO “and “value. GO” these two files.
It is easy to think of reflection’s core code as the reflect.valueof () and reflect.typeof () functions.
TypeOf() : reflect.typeof ()
// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
Copy the code
There are two data structures, one is Type and one is emptyInterface. Look at the code for both: emptyInterface is in the “GO SDK/ SRC /reflect/value.go” file
// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}
// nonEmptyInterface is the header for an interface value with methods.
type nonEmptyInterface struct {
// see .. /runtime/iface.go:/Itab
itab *struct {
ityp *rtype // static interface type
typ *rtype // dynamic concrete type
hash uint32 // copy of typ.hash
_ [4]byte
fun [100000]unsafe.Pointer // method table
}
word unsafe.Pointer
}
Copy the code
If you look closely, there are two structures: the empty interface and the interface that contains the method. And consistent with eface and iface content fields! Isn’t there eface and iFace? What’s the difference between the two?
After checking the code, I found:
Interface source code (located in the “Go to the SDK/SRC/runtime/runtime2. Go”) will eface and iface and reflection of the source code (located in the “Go SDK/ SRC /reflect/value.go “) emptyInterface and nonEmptyInterface keep data in sync!
In addition, the interface source (located in “Go SDK/ SRC /runtime/type.go”) and the rtype source (located in “Go SDK/ SRC /reflect/type.go”) also keep data synchronized.
For more exciting content, please follow my wechat official accountInternet Technology Nest
Or add wechat to discuss and exchange:
References:
Go 1.12.9 reflex source: / SRC/reflect/package Go 1.12.9 interface source: / SRC/runtime/runtime2. Go and other studygolang.com/articles/21… Blog.golang.org/laws-of-ref… Blog.csdn.net/u011957758/… Draveness. Me/golang/docs… Mp.weixin.qq.com/s/Hke0mSCEa…