Product | technology drops

The author | RaoChengQuan



Introduction: As a static language, Go is limited in flexibility compared to Python and other dynamic languages. But interface plus reflection enables the ability of a dynamic language to capture and even change type information and values dynamically while the program is running.

Reading Index


  • What is reflection

  • Why reflection

  • How is reflection implemented

    The types and the interface

    Basic function of reflection

    The three laws of reflection

  • Use of reflection correlation functions

    Code sample

    Unexported member

  • Practical application of reflection

    Json serialization

    Function and principle of DeepEqual

  • conclusion

  • The resources


What is reflection

Go straight to the definition on Wikipedia:

In computer science, reflection refers to the ability of a computer program to access, detect, and modify its own state or behavior during Run time. Reflection, figuratively speaking, is the ability of a program to “observe” and modify its behavior as it runs.

That begs the question: can’t it access, detect, and modify its own state and behavior at run time without reflection?

The answer to this question is to first understand what it means to access, detect and modify its own state or behavior. What is its nature?

In fact, it is the essence of the program at runtime to detect the type of object information and memory structure, without reflection can work? Can!!!! Using assembly language, dealing directly with the inner layer, what information cannot be obtained? However, when programming is migrated to a high-level language, it doesn’t work! The only way to achieve this skill is through reflection.

Reflection models vary from language to language, and some languages do not support reflection. The Go Language Bible defines reflection as follows:

The Go language provides a mechanism to update variables and check their values and call their methods at run time, but the exact types of these variables are not known at compile time. This is called reflection.


Why use reflection

There are two common scenarios that require reflection:

1. Sometimes you need to write a function, but you don’t know what type of argument is being passed to you. It may also be that many types are passed in and cannot be represented uniformly. That’s where reflection comes in.

2. Sometimes you need to decide which function to call based on certain criteria, such as user input. This is where the function and its parameters need to be reflected, executing the function dynamically at run time.

Before we talk about how reflection works and how to use it, here are a few reasons not to use reflection:

Reflection related code is often difficult to read. In software engineering, code readability is also a very important indicator.

2. Go language is a static language. During the coding process, the compiler can find some type errors in advance, but it is powerless to reflect the code. Therefore, including the code related to reflection is likely to run for a long time before errors occur, which is often a direct panic, which may cause serious consequences.

Reflection has a significant performance impact, running an order of magnitude or two slower than normal code. Therefore, for code that is critical to performance in a project, avoid using reflection features.

Reflection is how to achieve

Interface is a very powerful tool for implementing abstraction in the Go language. When an entity type is assigned to an interface variable, the interface stores the type information of the entity. Reflection is based on the type information of the interface.

The Go language defines various types in the Reflect package and implements various functions for reflection that can detect type information and change type values at run time.

▍ types and interface

In Go, each variable has a static type, which is determined at compile time, such as int,float64,[]int, etc. Note that this type is the declared type, not the underlying data type.

The official Go blog offers an example

type MyInt int 

var i int
 
var j MyInt

Copy the code

Although the underlying type of I and j is int, we know that they are different static types and cannot appear on both sides of the equal sign unless we cast them. The static type of j is MyInt.

Reflection is primarily related to the interface{} type. The underlying structure of interfaces was explored in the previous article on interfaces, so let’s review it again.

type iface struct{

    tab  *itab

    data unsafe.Pointer

}

type itab struct{

    inter  *interfacetype

    _type  *_type

    link   *itab

    hash   uint32

    bad    bool

    inhash bool

    unused [2]byte

    fun [1]uintptr

}
Copy the code

Itab consists of the specific type _type and interfaceType. _type represents a specific type, while interfacetype represents the interfacetype implemented by the specific type.



In fact, iFace describes a non-empty interface that contains methods; In contrast, eface describes an empty interface that contains no methods, and all types in the Go language “implement” an empty interface.

type eface struct{

    _type *_type

    data unsafe.Pointer

}
Copy the code

Compared to iFace, eFace is relatively simple. Only one _type field is maintained, representing the specific entity type hosted by the empty interface. Data describes specific values.



I’m going to use the Go official reflection blog as an example. Of course, I’ll explain it in graphic detail, but it’s a little bit clearer when you combine the two. By the way, technical people should not be afraid of English materials. To become technical experts, reading English raw materials is a necessary way to improve your skills.

Just to be clear: an interface variable can store any variable that implements all the methods defined by the interface.

The most common ones in Go are the Reader and Writer interfaces:

type Readerinterface{

    Read(p []byte) (n int, err error)

}

type Writerinterface{

    Write(p []byte) (n int, err error)

}

Copy the code

Next, there are various conversions and assignments between interfaces:

varr io.Reader

tty, err := os.OpenFile("/Users/qcrao/Desktop/test", os.O_RDWR, 0) iferr ! = nil{ returnnil, err } r = ttyCopy the code

Let’s first declare that r is of type IO.Reader, and notice that this is a static type of R, so its dynamic type is nil, and its dynamic value is nil.

Then, the statement r=tty changes the dynamic type of r to * os.file and the dynamic value to non-empty, indicating the open File object. At this point, r can be represented as

pairs:

.
,>
,type>



*os.File also contains a Write function, which implements the IO.Writer interface.

So the following assertion statement can be executed:

varw io.Writer

w = r.(io.Writer)

Copy the code

The reason we use assertions rather than direct assignments is because r’s static type is IO.Reader and does not implement the IO.Writer interface. The success of the assertion depends on whether r’s dynamic type meets the requirements.

Thus, w can also be represented as

, even though it is the same as r, but the function w can call depends on its static type io.writer, that is, it can only have such a call form: w.write ().
,>

The memory form of W is shown as follows:



Only the function corresponding to fun changes compared to r: Read->Write.

Finally, one more assignment:

varempty interface{}

empty = w
Copy the code

Since Empty is an empty interface, all types implement it, and w can be assigned to it without performing an assertion.




As you can see from the three diagrams above, interface contains three parts of information: _type is type information, *data refers to the actual value of the actual type, and ITab contains information about the actual type, including size, package path, and various methods bound to the type (methods are not drawn).

Here’s a sidebar about the os.file structure:




At the end of this section, let’s review one of the techniques mentioned in the previous article on interface, and show it here again:

First refer to the source code and define a “disguised” iFace and eface structure respectively.

type iface struct{

    tab  *itab

    data unsafe.Pointer

}

type itab struct{

    inter uintptr

    _type uintptr

    link uintptr

    hash  uint32

    _     [4]byte

    fun    [1]uintptr

}

type eface struct{

    _type uintptr

    data unsafe.Pointer

}

Copy the code

Next, force the memory contents occupied by the interface variable to the type defined above and print it:

package main

import(

   "os"

   "fmt"

   "io"

   "unsafe"

)

func main() {

    varr io.Reader

    fmt.Printf("initial r: %T, %v\n", r, r)

    tty, _ := os.OpenFile("/Users/qcrao/Desktop/test", os.O_RDWR, 0)

    fmt.Printf("tty: %T, %v\n", tty, tty) // Assign r to r = tty ftt.printf ("r: %T, %v\n", r, r)

    rIface := (*iface)(unsafe.Pointer(&r))

    fmt.Printf("r: iface.tab._type = %#x, iface.data = %#x\n", riface.tab. _type, riface.data) // Assign to w varw IO.Writer w = r.(IO.Writer) ftt.printf ("w: %T, %v\n", w, w)

    wIface := (*iface)(unsafe.Pointer(&w))

    fmt.Printf("w: iface.tab._type = %#x, iface.data = %#x\n", wiface.tab. _type, wiface.data) // Assign a value to empty varempty interface{} empty = w ftt.printf ("empty: %T, %v\n", empty, empty)

    emptyEface := (*eface)(unsafe.Pointer(&empty))

    fmt.Printf("empty: eface._type = %#x, eface.data = %#x\n", emptyEface._type, emptyEface.data)

}

Copy the code

Running results:

initial r: <nil>, <nil>

tty: *os.File, &{0xc4200820f0}

r: *os.File, &{0xc4200820f0}

r: iface.tab._type = 0x10bfcc0, iface.data = 0xc420080020

w: *os.File, &{0xc4200820f0}

w: iface.tab._type = 0x10bfcc0, iface.data = 0xc420080020

empty: *os.File, &{0xc4200820f0}

empty: eface._type = 0x10bfcc0, eface.data = 0xc420080020

Copy the code

R, w, empty have the same dynamic type and value. I won’t explain it in detail, but I can see it very clearly with the previous picture.

The basic function of reflection

The Reflect package defines an interface and a structure, reflect.type and Reflect.value, which provide functions to retrieve Type information stored in the interface.

Reflect. Type mainly provides type-specific information, so it is closely associated with _type; Reflect. Value combines both _type and data, so programmers can get or even change the Value of the type.

The Reflect package provides two basic reflection functions to get the above interfaces and constructs:

func TypeOf(i interface{}) Type

func ValueOf(i interface{}) Value
Copy the code

The TypeOf function is used to extract type information for values in an interface. Since its input argument is an empty interface{}, when this function is called, the argument is first converted to interface{}. In this way, the type, method set, and value of the argument are stored in the interface{} variable.

Take a look at the source:

func TypeOf(i interface{}) Type{

    eface := *(*emptyInterface)(unsafe.Pointer(&i))

    returntoType(eface.typ)

}
Copy the code

The emptyInterface here is the same as the eface mentioned above (the field name is slightly different, the field is the same) and is in a different source package: the former is in the Reflect package and the latter is in the Runtime package. Eface. Typ is a dynamic type.

type emptyInterface struct{

    typ  *rtype

    word unsafe.Pointer

}
Copy the code

As for the toType function, it just does a type conversion:

func toType(t *rtype) Type{

    ift == nil{

       return nil

    }
 
    returnt

}
Copy the code

Note that the return value Type is actually an interface that defines methods to get various types of information, while * rType implements the Type interface.

typeTypeinterface{// All types can call the following functions // the number of bytes used by the variables of this type Align() int // if it is a struct field, FieldAlign() int // Returns the I 'th (passed argument) Method in the Method set. Method(int) Method. Bool) // Obtain the number of exported methods in the type method set NumMethod() int // Type Name Name() string // Path of the returned type, for example: Encoding /base64 PkgPath() String // Size of return type, Similar to unsafe.sizeof Size() uintptr // the String representation of the return type String() String // The type value of the return type Kind() Kind // Whether the type Implements the interface u Implements(u) Type) bool AssignableTo(u Type) bool ConvertibleTo(U Type) bool Comparable() Bool // The following functions can only be called of certain types: Bits() int // Returns the direction of the channel, which can only be chan. ChanDir() ChanDir() // if t is of type func(x int, y...float// then t.isvariadic () ==trueIsVariadic() bool Returns the internal subelement Type. Elem() Type is called only by Array, Chan, Map, Ptr, or Slice. // if I exceeds the total number of fields, Panic Field(I int) StructField FieldByIndex(index []int) FieldByName(name) string) (StructField, FieldByNameFunc(match func(string)) // Returns the struct field with a name Bool) (StructField, bool) // Get the Type of the I parameter of the function Type In(I int) Type Len() int // Returns the number of fields of the Type, NumField() int // Returns the number of input parameters of the function type NumIn() int // Returns the number of return values of the function type NumOut() int // Returns the type of the ith value of the function type Out(I int) Common () *rtype uncommon() *uncommonType}Copy the code

As you can see, Type defines a number of methods through which you can get all the information about a Type. Be sure to go through all the above methods in their entirety.

Note that common, the penultimate method in the set of Type methods, returns the same Type of rtype as the _type in the previous article, and that the source code notes:


// rtype must be kept insync with .. /runtime/type.go:/^type._type.type rtype struct{

    size       uintptr

   ptrdata    uintptr

    hash       uint32

    tflag      tflag

    align      uint8

   fieldAlign  uint8

    kind       uint8

    alg        *typeAlg

    gcdata     *byte

    str        nameOff

    ptrToThis  typeOff

}
Copy the code

All types contain the rtype field, which represents common information for each type; In addition, different types contain some unique parts of themselves.

For example, arrayType and chanType contain rytPE, and arrayType contains slice, len, and other array-related information. The latter contains information that dir represents channel direction.

// arrayType represents a fixed array type.

type arrayType struct{

    rtype `reflect:"array"`

    elem *rtype // array element type

    slice *rtype // slice type 

    len   uintptr

}

// chanType represents a channel type.

type chanType struct{

    rtype `reflect:"chan"`

    elem *rtype // channel element type

    dir uintptr // channel direction (ChanDir)

}

Copy the code

Note that the Type interface implements the String() function, which satisfies the fmt.stringer interface, so when using fmt.println, the output is String(). In addition, the fmt.printf () function, if %T is used as the format argument, outputs the result of reflect.typeof, which is the dynamic type.

Such as:

fmt.Printf("%T", 3) // int
Copy the code

With TypeOf functions out of the way, let’s look at the ValueOf function. The return Value reflect.Value represents the actual variable stored in interface{}, which can provide various information about the actual variable. Related methods often require a combination of type information and value information.

For example, if you want to extract the field information of a structure, you need to use the field information about the structure held by the _type (in this case, structType) type, the offset information, and what *data points to — the actual value of the structure.

The source code is as follows:

func ValueOf(i interface{}) Value{

   ifi == nil{

     returnThe Value of {}} / /...returnUnpackEface (I interface{}) Value{e := (*emptyInterface)(unsafe.Pointer(&i)) t := e.typ ift == nil{return Value{}

    }

    f := flag(t.Kind())

    if ifaceIndir(t) {

        f |= flagIndir

    }

    return Value{t, e.word, f}

}
Copy the code

From the source, relatively simple: I is converted to the *emptyInterface type, and its TYP field and word field and a flag bit field are assembled into a Value structure. This is the return Value of ValueOf, which contains the pointer to the type structure, the address of the real data, and the flag bit.

The Value structure defines a number of methods by which you can directly manipulate the actual data pointed to by the Value field PTR:

Func (v Value) SetLen(n int) SetLen(n int) SetLen(n intcapFunc (v Value) SetCap(n int) func (v Value) SetMapIndex(key, Func (v Value) Index(I int) Value func (v Value) FieldByName(name String) Value / /...Copy the code

There are many other methods for the Value field.

Such as:

Func (v Value) int () int64 func (v Value) NumField() int // Attempt to send data to the channel (without blocking) func (v) int64 Value) TrySend(x reflect.Value) bool // Pass the parameter listinCall the function (or method func (v Value) Call(in[]Value) (r []Value) func (v Value) CallSlice(in[]Value) []Value

Copy the code

I’m not going to list them all, but there are a lot of them. Go to SRC /reflect/value.go and search func(vValue) for the source.

In addition, Interface, Type and Value can be connected through Type() and Interface() methods. The Type() method can also return Type information for a variable, equivalent to the reflect.typeof () function. The Interface() method restores Value to the original Interface.

Here is a picture from Lao Qian’s “Quick learn Go Language Lesson 15 – Reflection” :




To summarize: The TypeOf() function returns an interface that defines a set of methods that get all the information about a type; The ValueOf() function returns a structure variable containing the type information and the actual value.

Here’s a picture:



In the figure above, rtye implements the Type interface, which is a common part of all types. The emptyFace structure and the eface structure are the same thing, while the rtype and the _type are the same thing, except that some fields are slightly different. For example, the word field of emptyface and the data field of eface have different names, but the data type is the same.

Three laws of reflection

According to Go’s official reflection blog, there are three laws of reflection:

  1. Reflection goes from interface value to reflection object.

  2. Reflection goes from reflection object to interface value.

  3. To modify a reflection object, the value must be settable.


The first is the most basic: reflection is a mechanism for detecting types and values stored in an interface. This can be obtained via TypeOf and ValueOf functions.

The second is actually the opposite mechanism to the first, by reversing the return value from ValueOf into the Interface variable via the Interface() function.

Reflect. Type and reflect.Value can be converted to each other.

The third rule is a little trickier: if you need to manipulate a reflection variable, it must be settable. The essence of a reflection variable is that it stores the original variable itself, so that operations on a reflection variable are reflected in the original variable itself; Conversely, if the reflection variable does not represent the original variable, manipulating the reflection variable will have no effect on the original variable, which will cause confusion for the user. So the second case is not allowed at the linguistic level.

Take a classic example:

varx float64 = 3.4v := reflect.valueof (x) v.setFloat (7.1) // Error: will panic.Copy the code

Executing the above code causes panic because the reflection variable v does not represent x itself. Why? Because when reflect.valueof (x) is called, the argument passed inside the function is just a copy of the value passed, so v represents only a copy of x, so operating on v is prohibited.

Settable is a property of the reflection variable Value, but not all values are settable.

Just like in normal functions, when we want to change the variable passed in, we can use Pointers.

varx float64 = 3.4

p := reflect.ValueOf(&x)

fmt.Println("type of p:", p.Type())

fmt.Println("settability of p:", p.CanSet())

Copy the code

The output looks like this:

type of p: *float64

settability of p: false
Copy the code

P does not yet stand for x, p.lem () really stands for x, so you can actually manipulate x:

V := p.lem () v.setFloat (7.1) fmt.println (v.interface ()) // 7.1 fmt.println (x) // 7.1Copy the code

For the third item, remember: if you want to manipulate the source variable, the reflection variable Value must hold the address of the source variable.

Use of reflection related functions

Code examples

There are a lot of sample code using reflection in various blog articles on the Internet. After reading this article, there is basically nothing I can’t understand, haha! But here’s an example:

package main

import(

   "reflect"

   "fmt"

)

type Childstruct{

    Name string

    Grade int

    Handsome bool

}

type Adult struct{

     ID       string`qson:"Name"'Occupation string Handsome bool} // If the input parameter I is Slice and the element is a structure, a field named' Handsome 'is displayed, and a field has a tag or a field Name of' Name '. // If the value of the 'Name' field is' qcrao ', // set the value of the 'Name' field in the structure to 'Handsome'true. Func handsome(I interface{}) {Value v := reflect.valueof (I) // determine v is a Slice ifv.kind ()! = reflect.Slice{return} // Make sure that the element v is a structureife := v.Type().Elem(); e.Kind() ! = reflect.Struct{return} // Make sure the structure's field name contains"ID"Or json tag with 'name' // identifies the structure's field name"Handsome"St := v.type ().elem () // foundName := foundName :=false

    fori := 0; i < st.NumField(); i++ {

       f := st.Field(i)

     tag := f.Tag.Get("qson")

     if(tag == "Name"|| f.Name== "Name") && f.Type.Kind() == reflect.String{

        foundName = true

        break}}if! foundName {return

    }


    if niceField, foundHandsome := st.FieldByName("Handsome"); foundHandsome == false|| niceField.Type.Kind() ! = reflect.Bool{return} // Set the name to"qcrao"The object of"Handsome"Field istrue

   fori := 0; i < v.Len(); i++ {

      e := v.Index(i)

      handsome := e.FieldByName("Handsome"Varname reflect.Value forj := 0; varname := 0; j < st.NumField(); j++ { f := st.Field(j) tag := f.Tag.Get("qson")

       if tag == "Name"|| f.Name== "Name"{

          name = v.Index(i).Field(j)

       } 

     }

     if name.String() == "qcrao"{

        handsome.SetBool(true)  

     }

   }

}

func main() {

     children := []Child{

         {Name: "Ava", Grade: 3, Handsome: true},

         {Name: "qcrao", Grade: 6, Handsome: false},

    }

    adults := []Adult{

        {ID: "Steve", Occupation: "Clerk", Handsome: true},

        {ID: "qcrao", Occupation: "Go Programmer", Handsome: false},

    }

    fmt.Printf("adults before handsome: %v\n", adults)

    handsome(adults)

    fmt.Printf("adults after handsome: %v\n", adults)

    fmt.Println("-- -- -- -- -- -- -- -- -- -- -- -- --")

    fmt.Printf("children before handsome: %v\n", children)

    handsome(children)

    fmt.Printf("children after handsome: %v\n", children)

}

Copy the code

Code running results:

adults before handsome: [{SteveClerktrue} {qcrao GoProgrammerfalse}]

adults after handsome: [{SteveClerktrue} {qcrao GoProgrammertrue}]

-------------

children before handsome: [{Ava3true} {qcrao 6false}]

children after handsome: [{Ava3true} {qcrao 6true}]
Copy the code

The main thing the code does is to find out if the passed parameter is Slice and the Slice element is a structure, if there is a field Name of Name or a tag Name of Name, and another field Name is Handsome. If found, and the actual value of the field named Name is qcrao, set the value of the other field Handsome to true.

The program does not care what the structure passed in is, as long as its field Name contains Name and Handsome, both of which are objects that the Handsome function works with.

Note that the Adult structure qson:”Name” does not have Spaces in it, otherwise tag.get (“qson”) will not recognize it.

Did not export members

With reflection, unexported members of a structure can be read, but their values cannot be modified.

Note that normally, code cannot read unexported members of a structure, but reflection can override this restriction. Also, by reflection, the only members in the structure that can be modified are the exported members, that is, the first letter of the field name is uppercase.


A reflect.Value variable that takes the address records whether a structure member is an unexported member and rejects the modification if it is. CanAddr does not indicate whether a variable can be modified. CanSet checks if the corresponding reflect.Value is available and can be modified.

package main

import(

    "reflect"

    "fmt"

)

type Child struct{

     Name string

     handsome bool

}

func main() {

    qcrao := Child{Name: "qcrao", handsome: true}

    v := reflect.ValueOf(&qcrao)

    f := v.Elem().FieldByName("Name")

    fmt.Println(f.String())

    f.SetString("stefno")

    fmt.Println(f.String())

    f = v.Elem().FieldByName("handsome"// Cause cause panic, handsome field is not exported // f.setbool (true)

    fmt.Println(f.Bool())

}
Copy the code

Execution Result:

qcrao

stefno

true
Copy the code

In the above example, the Handsome field is not exported and can be read, but the related set method cannot be called, otherwise it will panic. Reflection must be used with care. Calling methods with mismatched types can cause panic.

The practical application of reflection

The practical application of reflection is very wide: IDE code auto-completion function, Object serialization (JSON function library), FMT related function implementation, ORM (full name: Object Relational Mapping, Object Relational Mapping)……

Here are two examples: json serialization and the DeepEqual function.

▍ json serialization

For those of you who have developed Web services, you’ve probably used the JSON data format. Json is a language-independent data format. It was originally used for real-time stateless data exchange between browsers and servers and developed from there.

Go provides two functions for serialization and deserialization:

func Marshal(v interface{}) ([]byte, error)

func Unmarshal(data []byte, v interface{}) error

Copy the code

Both functions take an interface as an argument, and are implemented using reflection-related features.

For both serialization and deserialization functions, you need to know all the fields of the parameter, including the field type and value, and then call the relevant GET or set function to perform the actual operation.

DeepEqual’s role and principles

In test functions, you often need a function that determines that the actual contents of two variables are exactly the same.

For example, how to determine that all elements of two slices are identical; How to determine whether the keys and values of two maps are the same?

The above problem can be implemented through the DeepEqual function.

func DeepEqual(x, y interface{}) bool

Copy the code

The DeepEqual function takes two interfaces, which can be typed as either true or flase to indicate whether the two input variables are of equal depth.

Just to be clear, if they are different types, even if the underlying type is the same and the corresponding value is the same, then they are not equal in “depth”.

type MyIntint

type YourIntint

func main() {

    m := MyInt(1)

    y := YourInt(1)

    fmt.Println(reflect.DeepEqual(m, y)) // false

}

Copy the code

In the above code, m and y are both underlying ints with values of 1, but they are statically typed differently. The former is MyInt and the latter YourInt, so they are not “deep” equal.

The DeepEqual function is annotated very clearly in the source code, listing how the different types of DeepEqual compare.

type

Equal depth case



In general, the implementation of DeepEqual simply needs to recursively call == to compare whether two variables are truly “deep” or not.

However, there are some exceptions: func types are not comparable and are “deep” only if both func types are nil; The float type, for precision reasons, also cannot be compared with ==; Struct, interface, array, etc., of type func or float.

For Pointers, when two Pointers are equal, they are equal in depth because they point to the same thing, even if they point to func or float, in which case they don’t care what the Pointers point to.

Similarly, for the same slice, both variables of the map have the same “depth”, regardless of the slice or map content.

For “looped” types, such as circular linked lists, the process of comparing whether the two Pointers have the same “depth” needs to mark the contents of the comparison. Once it is found that the two Pointers have been compared before, the comparison should be stopped immediately and the two Pointers are judged to have the same depth. The reason for doing this is to stop the comparison in time to avoid getting stuck in an infinite loop.

Look at the source code:

func DeepEqual(x, y interface{}) bool{

    ifx == nil|| y == nil{

       returnx == y

   }

   v1 := ValueOf(x)

   v2 := ValueOf(y)

   ifv1.Type() ! = v2.Type() {

       return false

   }

   return deepValueEqual(v1, v2, make(map[visit]bool), 0)

}
Copy the code

First check to see if either of them is nil, in which case the function returns true only if both are nil.

Next, using reflection, get the reflection objects of x and y, and immediately compare their types. Based on the previous content, this is actually a dynamic type, and return false if the types are different.

Finally, the core content is in the subfunction deepValueEqual.

The code is longer, but the idea is simple and clear: The core is a switch statement that identifies the different types of input parameters, recursively calls deepValueEqual, recursively down to the most basic data types, compares int, string, etc., and returns true or false. Finally, the comparison result of “depth” is equal.

In fact, all types of comparisons are similar, so here’s an excerpt from a slightly more complex map comparison:

// deepValueEqual function //......case Map:
   ifv1.IsNil() ! = v2.IsNil() {

       return false

  }

  ifv1.Len() ! = v2.Len() {

     return false

   }

   if v1.Pointer() == v2.Pointer() {

      return true

    }

    for_, k := range v1.MapKeys() {

       val1 := v1.MapIndex(k)

       val2 := v2.MapIndex(k)

       if! val1.IsValid() || ! val2.IsValid() || ! deepValueEqual(v1.MapIndex(k), v2.MapIndex(k), visited, depth+1) {return false}}return true/ /...Copy the code

The idea of comparing maps for equality is consistent with the table summarized above, and nothing more needs to be said. To clarify one thing, Visited is a map, which records the “pairs” compared in the recursive process:

type visit struct{

    a1 unsafe.Pointer

    a2 unsafe.Pointer

    typ Type

}

map[visit]bool
Copy the code

In the comparison process, if it is found that the “pair” of the comparison has already appeared in the map, the direct judgment of the “depth” comparison result is true.

▍ summary

As a static language, Go has limited flexibility when writing compared to a dynamic language such as Python. But interface plus reflection enables the ability of a dynamic language to capture and even change type information and values dynamically while the program is running.

Go’s reflection implementation is based on the type, or interface, and when we use reflection, we actually use the type-related information stored in the interface variable, commonly known as

pairs.
,value>

Only interface has reflection.

Reflection is implemented in the Reflect package and involves two related functions:

func TypeOf( i interface{} ) Type

func ValueOf( i interface{} ) Value
Copy the code

Type is an interface that defines a number of related methods to get Type information. Value holds the specific Value of the type. Type, Value, and Interface are converted to each other using functions TypeOf, ValueOf, and Interface.

Finally, let’s review the three laws of reflection:

1. Reflection goes from interface value to reflection object.

2. Reflection goes from reflection object to interface value.

3. To modify a reflection object, the value must be settable.

Translation:

1. Reflection Converts interface variables into reflection object types and values.

2. Reflection can be restored to the original interface variable through the reflection object Value.

Reflection can be used to change the value of a variable if the value can be changed.

Reference materials

Click here to learn more

▍ END