This is the 31st day of my participation in the August More Text Challenge

reflection

The Go language provides a mechanism to update variables, view values at run time, call methods, and operate directly on their layout without knowing their type at compile time, a mechanism called reflection

In most applications and services, reflection is not often used, but many frameworks rely on Go reflection to simplify code. We know that Go is not particularly expressive because of its few grammatical elements and simple design, but Go’s Reflect package makes up for some of its grammatical weaknesses

Reflect implements runtime reflection, allowing applications to operate on different types of objects. There are two very important pairs of functions and types in the reflection package:

  • TypeOf can get type information
  • Reflect.valueof The runtime representation that can retrieve data

The two types are reflect.Type and reflect.Value, which correspond to the above two functions

Type reflect.Type is an interface defined by the Reflect. package. We can use reflect.TypeOf to get the TypeOf any variable. The **reflect.Type interface defines some interesting methods. MethodByName gets a reference to a method of the current Type, and Implements determines whether the current Type Implements an interface. Below is the Type interface in Reflect

Type type interface {Align() int // when allocated in memory, Align returns the alignment of values of this type (in bytes) FieldAlign() int FieldAlign returns the alignment of the value of this type (in bytes) Method(int) Method // Method returns the i-th Method in the Method set of the type MethodByName(string) (Method, Bool) // MethodByName Returns the Method in the Method set of type with the Name and a Boolean indicating whether the Method was found. NumMethod() int NumMethod returns the number of methods that can be accessed using Method. Name() string PkgPath() string Size() uintptr String() string Kind() Kind Implements(u Type) bool ... }Copy the code

The reflect.Value Type in the reflect package is different from reflect.Type, which is declared as a structure. This structure has no exposed fields, but provides methods to get or write data. Details are as follows:

Type Value struct {// These members are private, // unexported typ *rtype // typ stores the type of Value represented by Value PTR.Pointer // flag to data // flag stores metadata about that Value} // Internal implementation of some functions func (v Value) Addr() Value func (v Value) Bool() bool func (v Value) Bytes() []byte func (v Value) CanAddr() boolCopy the code

All of the methods in the reflection package are basically designed around reflects. Type and reflect.Value. TypeOf and Reflect.valueof can convert an ordinary variable to the reflect.type and Reflect.value types provided in the reflection package, and you can then perform complex operations on them using the methods in the reflection package

The three laws

Runtime reflection is a way for a program to examine its own structure while it is running. The flexibility provided by reflection is a double-edged sword. Reflection as a metaprogramming method can reduce repetitive code, but overuse of reflection can make our program logic difficult to understand and slow down

  1. Reflection objects can be reflected from the interface{} variable
  2. The interface variable can be obtained from the reflection object
  3. To modify a reflection object, its value must be settable

The first law of

The first rule of reflection is that we can convert the interface{} variable of the Go language into reflection objects. Why go from interface{} variable to reflection object? When we execute reflect.valueof (1), it looks like we get the reflection type corresponding to the basic int, but since reflect.typeof and reflect.valueof are both of type interface{}, So a type conversion occurs during method execution

Because function calls in the Go language are passed by value, variables are cast when the function is called. The basic int type is converted to interface{}, which is why the first rule is from interface to reflection object, right

The reflect.TypeOf and Reflect. ValueOf functions mentioned above do this, and if we consider the Go language type and reflection type to be in two different worlds, these two functions are a bridge between the two worlds

TypeOf gets the TypeOf the variable author, and reflect.valueof gets the value draven. If we know the type and value of a variable, it means we know everything about the variable

package main

import (
	"fmt"
	"reflect"
)

func main() {
	author := "draven"
	fmt.Println("TypeOf author:", reflect.TypeOf(author))
	fmt.Println("ValueOf author:", reflect.ValueOf(author))
}

$ go run main.go
TypeOf author: string
ValueOf author: draven
Copy the code

Once we have the type of the variable, we can use the Method Method to get the Method implemented by the type, and use the Field Method to get all the fields contained by the type. For different types, we can also call different methods to get relevant information

  • StructField: Gets the number of fields and the field StructField by subscript and field name
  • Hash table: Gets the Key type of the hash table
  • Function or method: Gets the type of the input parameter and return value
  • .

In summary, reflect.TypeOf and reflect.ValueOf can be used to retrieve the reflection objects corresponding to variables in the Go language. Once the reflection object is retrieved, we can retrieve data and operations related to the current type and execute methods using the structure retrieved at runtime

The second law of

The second rule of reflection is that we can get the interface{} variable from the reflection object. Since it is possible to convert a variable of Interface type to a reflected object, some other method must be needed to restore the reflected object to a variable of Interface type. Reflect.value. Interface in Reflect does this

However, calling reflect.value. Interface can only get a variable of type Interface {}, which requires an explicit cast as shown below if you want to restore it to its original state

v := reflect.ValueOf(1)
v.Interface().(int)
Copy the code

The process from the reflected object to the interface value is the mirror process from the interface value to the reflected object. Both processes need to undergo two transformations

  • From interface value to reflection object:

    • Type conversions from primitive types to interface types;
    • Conversion from interface type to reflection object;
  • From reflected object to interface value:

    • The reflected object is converted to an interface type;
    • To a primitive type by an explicit cast

Of course, not all variables need to be cast. If the variable itself is of type interface{}, then it doesn’t need to be cast. Since casting is usually implicit, I don’t care much about it, except when we need to cast the reflected object back to its primitive type

The third law

The final rule of Go’s reflection has to do with whether a Value can be changed. If we want to update a reflect.Value, the Value it holds must be updatable, assuming we have the following code

func main() { i := 1 v := reflect.ValueOf(i) v.SetInt(10) fmt.Println(i) } $ go run reflect.go panic: reflect: reflect.Value.SetInt using unaddressable value goroutine 1 [running]: reflect.flag.mustBeAssignableSlow(0x82) /usr/local/go/src/reflect/value.go:260 +0x138 reflect.flag.mustBeAssignable(...)  /usr/local/go/src/reflect/value.go:247 reflect.Value.SetInt(0x10adc40, 0x11548c8, 0x82, 0xa) /usr/local/go/src/reflect/value.go:1637 +0x3b main.main() /Users/shulv/studySpace/GolangProject/src/go.language/ch12/reflect/reflect.go:19 +0xc5Copy the code

Running the above code causes the program to crash and reflect: Reflect. Flag. MustBeAssignable using unaddressable error value “, thinking it over carefully will be able to find the cause of the error: Since the function calls of Go language are all passed values, the reflection object we get has nothing to do with the original variable, so directly modifying the reflection object will not change the original variable, and the program will crash in order to prevent errors

To modify the original variable, you can only use the following method

func main() {
	i := 1
	v := reflect.ValueOf(&i)
	v.Elem().SetInt(10)
	fmt.Println(i)
}

$ go run reflect.go
10
Copy the code
  1. Call reflect.valueof to get a variable pointer
  2. Call reflect.value. Elem to get the variable to which the pointer points
  3. Call reflect.value. SetInt to update the Value of the variable

Since all function calls in Go are passed by Value, we can only change the original variable in a roundabout way: first get the reflect.Value corresponding to the pointer, and then get the variable that can be set through the reflect.value. Elem method. We can understand this process through the following code:

func main() {
    i := 1
    v := &i
    *v = 10
}
Copy the code

If we can’t manipulate the I variable directly to change the value it holds, we can only get the address of the I variable and use *v to change the integer stored there

Type and value

The interface{} type of the Go language is represented internally by the reflect.emptyInterface cluster, where the rTYPE field is used to indicate the type of the variable and the word field refers to the internally encapsulated data

type emptyInterface struct {
	typ  *rtype
	word unsafe.Pointer
}
Copy the code

The reflect.TypeOf function used to get the variable type implicitly converts the passed variable to type reflect.emptyInterface and gets the type information stored therein reflect.rtype

func TypeOf(i interface{}) Type {
	eface := *(*emptyInterface)(unsafe.Pointer(&i))
	return toType(eface.typ)
}

func toType(t *rtype) Type {
	if t == nil {
		return nil
	}
	return t
}
Copy the code

Reflect.rtype is a structure that implements the reflect.Type interface. The reflect.rtype.String method that this structure implements helps us get the name of the current Type

func (t *rtype) String() string { s := t.nameOff(t.str).name() if t.tflag&tflagExtraStar ! = 0 { return s[1:] } return s }Copy the code

The implementation of reflect.TypeOf is not really complicated, it simply converts an interface{} variable into an internal reflect.emptyInterface representation, from which the corresponding type information is retrieved

The reflect.ValueOf function that gets the interface Value reflect.Value is also very simple to implement. We call reflect.escapes to ensure that the current Value escapes to the heap. We then get the reflect.Value structure from the interface via reflect.unpackEface:

func ValueOf(i interface{}) Value { if i == nil { return Value{} } escapes(i) return unpackEface(i) } func unpackEface(i  interface{}) Value { e := (*emptyInterface)(unsafe.Pointer(&i)) t := e.typ if t == nil { return Value{} } f := flag(t.Kind()) if ifaceIndir(t) { f |= flagIndir } return Value{t, e.word, f} }Copy the code

Reflect. unpackEface converts the incoming interface to reflect.emptyInterface and returns the concrete type and pointer wrapped as an reflect.Value structure

When we want to convert a variable to a reflection object, Go does the conversion at compile time, converting the variable’s type and value to interface{} and waiting for the reflect package to fetch the information stored in the interface at runtime

To update the variable

When we want to update reflect.Value, we call reflect.value. Set to update the reflection object, This method will be called reflect. Flag. MustBeAssignable and reflect flag. MustBeExported respectively to check whether the current reflection object can be set as well as the field is closed to the public:

func (v Value) Set(x Value) { v.mustBeAssignable() x.mustBeExported() var target unsafe.Pointer if v.kind() == Interface  { target = v.ptr } x = x.assignTo("reflect.Set", v.typ, target) typedmemmove(v.typ, v.ptr, x.ptr) }Copy the code

Reflect.value. Set calls reflect.value. assignTo and returns a new reflection object pointer that overwrites the original reflection variable

func (v Value) assignTo(context string, dst *rtype, target unsafe.Pointer) Value {
	...
	switch {
	case directlyAssignable(dst, v.typ):
		...
		return Value{dst, v.ptr, fl}
	case implements(dst, v.typ):
		if v.Kind() == Interface && v.IsNil() {
			return Value{dst, nil, flag(Interface)}
		}
		x := valueInterface(v, false)
		if dst.NumMethod() == 0 {
			*(*interface{})(target) = x
		} else {
			ifaceE2I(dst, x, target)
		}
		return Value{dst, target, flagIndir | flag(Interface)}
	}
	panic(context + ": value of type " + v.typ.String() + " is not assignable to type " + dst.String())
}
Copy the code

Reflect.value. assignTo creates a new reflect.Value structure based on the current and set reflection object types:

  • If the types of two reflection objects are directly interchangeable, the target reflection object is returned
  • If the current reflection object is an interface and the target object implements the interface, the target object is simply wrapped as the interface value

During variable updates, the pointer to reflect.Value returned by reflect.value. assignTo overwrites the pointer to the current reflected object to update the variable

reference

The Go Programming Language — Alan A. A. Donovan

Go Language Learning Notes — Rain Marks

Go language design and implementation – reflection