Hi, I’m Mingo.
In their learning Golang this time, I wrote a detailed study notes on my personal number WeChat public Go programming time, for the language, I was a beginner, so writing should fit in with the new to classmates, if you are just learning the language, don’t focus on prevention, study together, Grow together.
My online blog: golang.iswbm.com
My Github:github.com/iswbm/GolangCodingTime
When I was working with Python, I could even do something immediately with internal savings without knowing what introspection and reflection were.
However, after learning Go, reflection has become a difficulty for me, and I always feel that the concept of reflection object is extremely abstract.
Like the previous article, this article will try to use diagrams to explain some abstract concepts. If I understand something wrong, I hope you can give me a message at the end of the article to correct it. Thank you.
About the reflection of the content, I divided into several, this one is the introduction, will start from the classic three laws of reflection, write some demo code, tell you the basic content of reflection.
1. The real world versus the reflective world
In this article, to distinguish the variable value types before and after reflection, I refer to the pre-reflection environment as the real world and the post-reflection environment as the reflection world. It’s a loose analogy, but it’s helpful to me, and HOPEFULLY useful to you.
In the world of reflection, we have the ability to get the type, properties, and methods of an object.
2. Two types: Type and Value
In the world of Go reflection, there are two types that are important and central to the whole reflection thing, and you’ll need to look at them before you learn how to use the Reflect package:
- reflect.Type
- reflect.Value
They correspond to type and value in the real world, respectively, but in reflection objects, they have more content.
From the source, reflect.Type exists as an interface
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() Type
Len() int
NumField() int
NumIn() int
NumOut() int
Out(i int) Type
common() *rtype
uncommon() *uncommonType
}Copy the code
And reflect.Value exists as a structure,
type Value struct {
typ *rtype
ptr unsafe.Pointer
flag
}Copy the code
At the same time, it receives many methods (see table below), which cannot be described here due to space constraints.
Addr Bool Bytes runes CanAddr CanSet Call CallSlice call Cap Close Complex Elem Field FieldByIndex FieldByName FieldByNameFunc Float Index Int CanInterface Interface InterfaceData IsNil IsValid IsZero Kind Len MapIndex MapKeys MapRange Method NumMethod MethodByName NumField OverflowComplex OverflowFloat OverflowInt OverflowUint Pointer Recv recv Send send Set SetBool SetBytes setRunes SetComplex SetFloat SetInt SetLen SetCap SetMapIndex SetUint SetPointer SetString Slice Slice3 String TryRecv TrySend Type Uint UnsafeAddr assignTo ConvertCopy the code
From the previous section (), we learned that an interface variable is actually composed of a pair (type and data) that records the value and type of the actual variable. That is, in the real world, type and value are combined to form interface variables.
In the reflective world, type and data are separated, and they are represented by reflect. type and reflect.Value respectively.
3. Read the three laws of reflection
There are three laws of reflection in Go, and they’re important to use when you’re learning about reflection:
- Reflection goes from interface value to reflection object.
- Reflection goes from reflection object to interface value.
- To modify a reflection object, the value must be settable.
Translation:
- Reflection can convert interface type variables into “reflection type objects”;
- Reflection can convert a reflection type object to an interface type variable.
- If you want to modify a Reflection type object, its type must be writable;
The first law of
Reflection goes from interface value to reflection object.
To convert from an interface variable to a reflection object, we need to mention two important methods in the Reflect package:
- Reflect.typeof (I) : gets the TypeOf the interface value
- Reflect.valueof (I) : gets the ValueOf the interface value
The objects returned by these two methods are called reflection objects: Type Object and Value Object.
For example, how are these two methods used?
Package main import (" FMT ""reflect") func main() {var age interface{} = 25 fmt.printf (" Value: %v \n", age, age) t := reflect.typeof (age) v := reflect.valueof (age) // From interface variable to reflection object fmt.Printf(" From interface variable to reflection object: Printf(" From interface variable to reflection object: Value object of Type %T \n", v)}Copy the code
The output is as follows
The original interface variable is of Type int and has a Value of 25. From the interface variable to the reflected object: Type The object is of Type *reflect.rtype From the interface variable to the reflected object: Value The object is of Type reflect.ValueCopy the code
This completes the conversion from an interface type variable to a reflection object.
Wait, didn’t we define age above as an int? Why does the first rule say interface type?
We already mentioned this in the previous section (the three “unspoken rules” for interfaces). Because TypeOf and ValueOf accept an empty interface type, and Go functions are value-passing, Go implicitly converts our types to interface types.
// TypeOf returns the reflection Type of the value in the interface{}.TypeOf returns nil.
func TypeOf(i interface{}) 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{}) ValueCopy the code
The second law of
Reflection goes from reflection object to interface value.
Contrary to the first law, the second law describes the conversion from reflection objects to interface variables.
The reflect.Value constructor accepts the Interface method and returns a variable of type Interface {}. Only Value can be reversed, not Type. If Type can be reversed, what can it be?)
// Interface returns v's current value as an interface{}.
// It is equivalent to:
// var i interface{} = (v's underlying value)
// It panics if the Value was obtained by accessing
// unexported struct fields.
func (v Value) Interface() (i interface{}) {
return valueInterface(v, true)
}Copy the code
This function is a bridge we use to implement the conversion of reflection objects into interface variables.
Examples are as follows
Package main import (" FMT ""reflect") func main() {var age interface{} = 25 fmt.printf (" Value: %v \n", age, age) t := reflect.typeof (age) v := reflect.valueof (age) // From interface variable to reflection object fmt.Printf(" From interface variable to reflection object: Type Object of Type %T \n", T) FMT.Printf(" From interface variable to reflection object: Value object type %T \n", v) // From reflection object to interface variable I := v.interface () fmt.printf (" From reflection object to interface variable: new object type %T Value %v \n", I, I)}Copy the code
The output is as follows
The original interface variable is of Type int and has a Value of 25. From the interface variable to the reflected object: Type The object is of Type *reflect.rtype From the interface variable to the reflected object: Value The object is of Type reflect.Value From the reflected object to the interface variable: The new object is of type int and the value is 25Copy the code
Of course, the last converted object, statically typed, is interface{}. To convert to the original primitive type, you need to convert the type assertion, which I explained in the previous section. You can click here to review :().
i := v.Interface().(int)Copy the code
So far, we’ve learned about the two laws of reflection, and I’ve drawn a picture of how these two laws are understood, and you can use the picture below to help you understand and memorize them.
The third law
To modify a reflection object, the value must be settable.
The reflective world is a “reflection” of the real world, a description I make, but it’s not strictly true, because not everything you do in the reflective world reverts to the real world.
The third law introduces the concept of settable (or writable).
As we have stated in previous articles, functions in Go are passed by value, and as long as you do not pass a pointer to a variable, changes you make inside the function do not affect the original variable.
Returning to reflection, when you use reflect.typeof and reflect.valueof, if you pass a pointer to a variable other than the interface variable, the Valueof the variable in the reflection world will always be a copy of the real world, and the changes you make to the reflection object will not be reflected in the real world.
So in the rules of reflection
- Reflection objects that are not created by receiving Pointers to variables are not “writable”
- Whether or not”writability“, can be used
CanSet()
To get information - Modifying an object that is not “writable” makes no sense and is considered illegal, so an error is reported.
Package main import (" FMT ""reflect") func main() {var name string = "Go" v := reflect.valueof (name) Println(" writability is :", v.canset ())}Copy the code
The output is as follows
Writability is: falseCopy the code
To make a reflection object writable, two things need to be noted
- Pass a pointer to a variable when creating a reflection object
- use
Elem()
The function returns the data to which the pointer points
The complete code is as follows
Package main import (" FMT ""reflect") func main() {var name string = "Go" v1 := reflect.valueof (&name) Println("v1 writable :", v1.canset ()) v2 := v1.elem () writable :", v2.canset ())}Copy the code
The output is as follows
The writability of v1 is false. The writability of v2 is trueCopy the code
Now that you know how to make an object in the reflective world writable, it’s time to see how to update it for modifications.
Reflection objects have the following methods that start with the word Set
These methods are our entry points for modifying values.
Let’s take an example
Package main import (" FMT ""reflect") func main() {var name string = "Go programming time" FMT.Println(" ", name) v1 := reflect.valueof (&name) v2 := v1.elem () v2.setString ("Python Programming time ") FMT.Println(" After updating the reflected object, the real world name becomes: ", name) }Copy the code
The output is as follows
The original value of name in the real world is: Go Programming time After updating the reflection object, name in the real world becomes: Python programming timeCopy the code
Refer to the article
- Reflection: The Three laws of Go language reflex