Introduction
Reflection is a mechanism that allows you to see the composition of the structure and update the value without knowing the specific type at compile time. Using reflection allows us to write code that can handle all types in a unified manner. Even types that didn't exist when this part of the code was written. A specific example is the fmt.Println()
method, which can print out our custom structure type.
Although, generally speaking, it is not recommended to use reflection in the code. Reflection affects performance, is not easy to read, and postpones type problems that can be checked at compile time to be manifested in panic form at runtime. These are the shortcomings of reflection. However, I think reflection must be mastered for the following reasons:
- Many standard libraries and third-party libraries use reflection, although the exposed interface is encapsulated, and there is no need to understand reflection. But if you want to study these libraries in depth, understand the implementation, and read the source code, reflection cannot be bypassed. For example,
encoding/json
,encoding/xml
etc.; - If there is a requirement to write a function or method that can handle all types, we must use reflection. Because the number of types in Go is unlimited and you can customize types, using type assertions cannot achieve your goal.
The Go language standard library reflect
provides the reflection function.
interface
Reflection is built on Go's type system and is closely related to interfaces.
First, let's briefly introduce the interface. The interface in the Go language stipulates a set of methods, and any variable that defines the type of this set of methods (also known as implementing the interface) can be assigned to the variable of the interface.
package main
import "fmt"
type Animal interface {
Speak()
}
type Cat struct {
}
func (c Cat) Speak() {
fmt.Println("Meow")
}
type Dog struct {
}
func (d Dog) Speak() {
fmt.Println("Bark")
}
func main() {
var a Animal
a = Cat{}
a.Speak()
a = Dog{}
a.Speak()
}
In the above code, we defined a Animal
interface, which agreed on a method Speak()
. Then two structure types Cat
and Dog
are defined, both of which define this method. In this way, we can assign Cat
and Dog
objects to variables of type Animal
The interface variable contains two parts: type and value, namely (type, value)
. The type is the type of the value assigned to the interface variable, and the value is the value assigned to the interface variable. If we know the type of variables stored in the interface, we can also use type assertions to get the value of a specific type through interface variables:
type Animal interface {
Speak()
}
type Cat struct {
Name string
}
func (c Cat) Speak() {
fmt.Println("Meow")
}
func main() {
var a Animal
a = Cat{Name: "kitty"}
a.Speak()
c := a.(Cat)
fmt.Println(c.Name)
}
In the above code, we know that the interface a
saves the Cat
object, and directly uses the type assertion a.(Cat)
obtain the Cat
object. However, if the type asserted does not match the actual stored type, it will panic directly. Therefore, in actual development, another type of assertion form c, ok := a.(Cat)
is usually used. If the type does not match, this form will not panic, but by setting the second return value to false to indicate this situation .
Sometimes, a type defines many methods, not just the methods agreed upon by the interface. Through the interface, we can only call the method agreed in the interface. Of course, we can also assert its type as another interface, and then call the method agreed on this interface, provided that the original object implements this interface:
var r io.Reader
r = new(bytes.Buffer)
w = r.(io.Writer)
io.Reader
and io.Writer
are the two most frequently used interfaces in the standard library:
// src/io/io.go
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
bytes.Buffer
implements these two interfaces at the same time, so the byte.Buffer
object can be assigned to the io.Reader
variable r
, and then r
can be asserted as io.Writer
, because the value stored in the io.Reader
io.Writer
interface.
If an interface A
contains another interface B
all methods, the interface A
variables can be assigned directly to B
variables, because A
value stored in certain implements A
all the prescribed manner, then certainly achieved B
. At this time, no type assertion is required. For example, the standard library io
also defines a io.ReadCloser
interface, this interface variable can be directly assigned to io.Reader
:
// src/io/io.go
type ReadCloser interface {
Reader
Closer
}
empty interface interface{}
is a special interface, it does not agree on any method. All types of values can be assigned to variables of the empty interface type, because it does not have any method to limit .
One point is particularly important. is type assertion or direct assignment between (type, value)
type-value pair stored in its internal storage remains unchanged. It's just that the methods that can be called through different interfaces are different . For this reason, interface variable must not be the interface type .
With the basic knowledge of these interfaces, let's introduce reflection below.
Reflection basis
The reflection function in Go language is provided by the reflect
package. reflect
package defines an interface reflect.Type
and a structure reflect.Value
, which define a large number of methods for obtaining type information, setting values, and so on. Inside the reflect
package, only the type descriptor implements the reflect.Type
interface. Since the type descriptor is an unexported type, we can only get the value of the reflect.Type
reflect.TypeOf()
package main
import (
"fmt"
"reflect"
)
type Cat struct {
Name string
}
func main() {
var f float64 = 3.5
t1 := reflect.TypeOf(f)
fmt.Println(t1.String())
c := Cat{Name: "kitty"}
t2 := reflect.TypeOf(c)
fmt.Println(t2.String())
}
Output:
float64
main.Cat
Go language is statically typed, and each variable has and can only have one definite and known type at compile time, that is, the static type of the variable. The static type is determined when the variable is declared and cannot be modified. An interface variable, its static type is the interface type. Although different types of values can be assigned to it at runtime, only its internal dynamic type and dynamic value are changed. Its static type has never changed.
reflect.TypeOf()
method is used to take out the dynamic type part of the interface and return it reflect.Type
and many more! The above code does not seem to have an interface type?
Let's look at the definition of reflect.TypeOf()
// src/reflect/type.go
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
It accepts a interface{}
type 060be428aa10fc, so the above float64
and Cat
variables will be converted to interface{}
and then passed to the method. The reflect.TypeOf()
method gets the type part in interface{}
Correspondingly, the reflect.ValueOf()
method is naturally to obtain the value part of the interface, and the return value is of type reflect.Value
Add the following code on the basis of the above example:
v1 := reflect.ValueOf(f)
fmt.Println(v1)
fmt.Println(v1.String())
v2 := reflect.ValueOf(c)
fmt.Println(v2)
fmt.Println(v2.String())
Run output:
3.5
<float64 Value>
{kitty}
<main.Cat Value>
Because fmt.Println()
will do special processing for the reflect.Value
type and print its internal value, it shows that the reflect.Value.String()
method is called to obtain more information.
The acquisition type is so common, fmt
provides the formatting symbol %T
output parameter type:
fmt.Printf("%T\n", 3) // int
The types in Go are unlimited, and new types type
However, the types of types are limited. All types of enumerations are defined in the reflect
// src/reflect/type.go
type Kind uint
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)
There are 26 kinds in total, and we can classify them as follows:
- Basic types
Bool
,String
and various numeric types (signed integerInt/Int8/Int16/Int32/Int64
, unsigned integerUint/Uint8/Uint16/Uint32/Uint64/Uintptr
, floating point numberFloat32/Float64
, complex numberComplex64/Complex128
) - Composite (aggregate) types
Array
andStruct
- Reference types
Chan
,Func
,Ptr
,Slice
andMap
(The distinction between value type and reference type is not obvious, and I don’t quote the conflict here, you just need to understand the meaning) - Interface type
Interface
- Illegal type
Invalid
, it represents no value (reflect.Value
zero value is theInvalid
type)
All types in Go (including custom types) are the above types or their combination.
E.g:
type MyInt int
func main() {
var i int
var j MyInt
i = int(j) // 必须强转
ti := reflect.TypeOf(i)
fmt.Println("type of i:", ti.String())
tj := reflect.TypeOf(j)
fmt.Println("type of j:", tj.String())
fmt.Println("kind of i:", ti.Kind())
fmt.Println("kind of j:", tj.Kind())
}
The static types of the above two variables are int
and MyInt
respectively, which are different. Although MyInt
's type (underlying type) is int
. The assignment between them must be forced type conversion. But their types are the same, both are int
.
The code output is as follows:
type of i: int
type of j: main.MyInt
kind of i: int
kind of j: int
Reflection usage
Since there are many reflection content and APIs, we will introduce them in combination with specific usage.
Pivot data composition
The composition of the perspective structure requires the following methods:
reflect.ValueOf()
: Obtain the reflection value object;reflect.Value.NumField()
: Get the number of fields from the reflection value object of the structure;reflect.Value.Field(i)
: Obtain the reflection value object of thei
field from the reflection value object of the structure;reflect.Kind()
: Obtain the type from the reflection value object;reflect.Int()/reflect.Uint()/reflect.String()/reflect.Bool()
: These methods extract the specific type from the reflection value object.
Example:
type User struct {
Name string
Age int
Married bool
}
func inspectStruct(u interface{}) {
v := reflect.ValueOf(u)
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
switch field.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
fmt.Printf("field:%d type:%s value:%d\n", i, field.Type().Name(), field.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
fmt.Printf("field:%d type:%s value:%d\n", i, field.Type().Name(), field.Uint())
case reflect.Bool:
fmt.Printf("field:%d type:%s value:%t\n", i, field.Type().Name(), field.Bool())
case reflect.String:
fmt.Printf("field:%d type:%s value:%q\n", i, field.Type().Name(), field.String())
default:
fmt.Printf("field:%d unhandled kind:%s\n", i, field.Kind())
}
}
}
func main() {
u := User{
Name: "dj",
Age: 18,
Married: true,
}
inspectStruct(u)
}
Combination reflect.Value
the NumField()
and Field()
methods may traverse each field structure. Then do the corresponding processing Kind
each field.
Some methods can only be called when the original object is of a certain type. For example, the NumField()
and Field()
methods can only be called when the original object is a structure, otherwise it will be panic
.
After identifying the specific type, you can call the corresponding type method of the reflection value object to obtain the value of the specific type, such as field.Int()/field.Uint()/field.Bool()/field.String()
above. But in order to reduce the burden of processing, the Int()/Uint()
method merges the types, they only return the corresponding types with the largest range, Int()
returns the Int64
type, and Uint()
returns the Uint64
type. And Int()/Uint()
internally process the corresponding signed or unsigned type, and turn Int64/Uint64
back to 060be428aa1711. The following is the implementation reflect.Value.Int()
// src/reflect/value.go
func (v Value) Int() int64 {
k := v.kind()
p := v.ptr
switch k {
case Int:
return int64(*(*int)(p))
case Int8:
return int64(*(*int8)(p))
case Int16:
return int64(*(*int16)(p))
case Int32:
return int64(*(*int32)(p))
case Int64:
return *(*int64)(p)
}
panic(&ValueError{"reflect.Value.Int", v.kind()})
}
In the above code, we only deal with a small number of categories. In actual development, perfect processing requires a lot of effort, especially when the field is of other complex types, or even contains circular references.
In addition, we can also see through the structure in the standard library, and we can see through the unexported field . Use the inspectStruct()
method defined above:
inspectStruct(bytes.Buffer{})
bytes.Buffer
is as follows:
type Buffer struct {
buf []byte
off int
lastRead readOp
}
They are all unexported fields, the program output:
field:0 unhandled kind:slice
field:1 type:int value:0
field:2 type:readOp value:0
To see the map
, the following methods are required:
reflect.Value.MapKeys()
reflect.Value
objects of each key into a slice;reflect.Value.MapIndex(k)
: Thereflect.Value
object of the incoming key, and the return value ofreflect.Value
;- Then you can perform the same processing as above for the key and value
reflect.Value
Example:
func inspectMap(m interface{}) {
v := reflect.ValueOf(m)
for _, k := range v.MapKeys() {
field := v.MapIndex(k)
fmt.Printf("%v => %v\n", k.Interface(), field.Interface())
}
}
func main() {
inspectMap(map[uint32]uint32{
1: 2,
3: 4,
})
}
I am here to lazy, not for each Kind
do processing, direct call key - value reflect.Value
of Interface()
method. This method returns the value contained inside in the form of an empty interface. Program output:
1 => 2
3 => 4
Similarly, the MapKeys()
and MapIndex(k)
methods can only be called when the original object is of map
, otherwise it will be panic
.
For perspective slice or array composition, the following methods are required:
reflect.Value.Len()
: Returns the length of the array or slice;reflect.Value.Index(i)
: Returns the value ofreflect.Value
i
- Then the
reflect.Value
judgmentKind()
is processed.
Example:
func inspectSliceArray(sa interface{}) {
v := reflect.ValueOf(sa)
fmt.Printf("%c", '[')
for i := 0; i < v.Len(); i++ {
elem := v.Index(i)
fmt.Printf("%v ", elem.Interface())
}
fmt.Printf("%c\n", ']')
}
func main() {
inspectSliceArray([]int{1, 2, 3})
inspectSliceArray([3]int{4, 5, 6})
}
Program output:
[1 2 3 ]
[4 5 6 ]
Similarly, the Len()
and Index(i)
methods can only be called when the original object is a slice, array or string, and other types will be panic
.
The perspective function type requires the following methods:
reflect.Type.NumIn()
: Get the number of function parameters;reflect.Type.In(i)
: Get thei
of thereflect.Type
parameter;reflect.Type.NumOut()
: Get the number of function return values;reflect.Type.Out(i)
: Get the firsti
return value ofreflect.Type
.
Example:
func Add(a, b int) int {
return a + b
}
func Greeting(name string) string {
return "hello " + name
}
func inspectFunc(name string, f interface{}) {
t := reflect.TypeOf(f)
fmt.Println(name, "input:")
for i := 0; i < t.NumIn(); i++ {
t := t.In(i)
fmt.Print(t.Name())
fmt.Print(" ")
}
fmt.Println()
fmt.Println("output:")
for i := 0; i < t.NumOut(); i++ {
t := t.Out(i)
fmt.Print(t.Name())
fmt.Print(" ")
}
fmt.Println("\n===========")
}
func main() {
inspectFunc("Add", Add)
inspectFunc("Greeting", Greeting)
}
NumIn()/In()/NumOut()/Out()
only when the original object is a function type, and other types will be panic
.
Program output:
Add input:
int int
output:
int
===========
Greeting input:
string
output:
string
===========
The method defined in the perspective structure requires the following methods:
reflect.Type.NumMethod()
: Returns the number of methods defined by the structure;reflect.Type.Method(i)
: return thereflect.Method
object ofi
Example:
func inspectMethod(o interface{}) {
t := reflect.TypeOf(o)
for i := 0; i < t.NumMethod(); i++ {
m := t.Method(i)
fmt.Println(m)
}
}
type User struct {
Name string
Age int
}
func (u *User) SetName(n string) {
u.Name = n
}
func (u *User) SetAge(a int) {
u.Age = a
}
func main() {
u := User{
Name: "dj",
Age: 18,
}
inspectMethod(&u)
}
reflect.Method
defined as follows:
// src/reflect/type.go
type Method struct {
Name string // 方法名
PkgPath string
Type Type // 方法类型(即函数类型)
Func Value // 方法值(以接收器作为第一个参数)
Index int // 是结构体中的第几个方法
}
In fact, reflect.Value
also defines these methods NumMethod()/Method(i)
The difference is: reflect.Type.Method(i)
returns a reflect.Method
object, and you can get information about the method name, type, and the first method in the structure. If you want to reflect.Method
, you must use the Func
field, and you must pass in the receiver's reflect.Value
as the first parameter:
m.Func.Call(v, ...args)
But reflect.Value.Method(i)
return a reflect.Value
objects, it always calls Method(i)
method reflect.Value
as a receiver objects, no additional incoming. And directly use Call()
initiate a method call:
m.Call(...args)
reflect.Type
and reflect.Value
have many methods with the same name, so you need to pay attention to discrimination when using them.
Call function or method
To call a function, the following methods are required:
reflect.Value.Call()
: Usereflect.ValueOf()
generate reflection value objects for each parameter, and then compose slices and pass them to theCall()
method.Call()
method executes the function call and returns[]reflect.Value
. Each element is a reflection value object of the original return value.
Example:
func Add(a, b int) int {
return a + b
}
func Greeting(name string) string {
return "hello " + name
}
func invoke(f interface{}, args ...interface{}) {
v := reflect.ValueOf(f)
argsV := make([]reflect.Value, 0, len(args))
for _, arg := range args {
argsV = append(argsV, reflect.ValueOf(arg))
}
rets := v.Call(argsV)
fmt.Println("ret:")
for _, ret := range rets {
fmt.Println(ret.Interface())
}
}
func main() {
invoke(Add, 1, 2)
invoke(Greeting, "dj")
}
We encapsulate a invoke()
method, interface{}
empty interface, and receive function call parameters interface{}
Inside the function, first call the reflect.ValueOf()
method to obtain the reflection value object of the function object. reflect.ValueOf()
for each parameter in turn to generate the reflection value object slice of the parameter. Call()
method of the function reflection value object to output the return value.
Program running results:
ret:
3
ret:
hello dj
The method call is similar:
type M struct {
a, b int
op rune
}
func (m M) Op() int {
switch m.op {
case '+':
return m.a + m.b
case '-':
return m.a - m.b
case '*':
return m.a * m.b
case '/':
return m.a / m.b
default:
panic("invalid op")
}
}
func main() {
m1 := M{1, 2, '+'}
m2 := M{3, 4, '-'}
m3 := M{5, 6, '*'}
m4 := M{8, 2, '/'}
invoke(m1.Op)
invoke(m2.Op)
invoke(m3.Op)
invoke(m4.Op)
}
operation result:
ret:
3
ret:
-1
ret:
30
ret:
4
The above is to initiate a call when the method name is clearly known at compile time. What if you only give a structure object and specify which method to call through a parameter? This requires the following methods:
reflect.Value.MethodByName(name)
: Get the name structure definedname
method ofreflect.Value
objects, this method has the default receiver parameters, i.e. callingMethodByName()
methodreflect.Value
.
Example:
type Math struct {
a, b int
}
func (m Math) Add() int {
return m.a + m.b
}
func (m Math) Sub() int {
return m.a - m.b
}
func (m Math) Mul() int {
return m.a * m.b
}
func (m Math) Div() int {
return m.a / m.b
}
func invokeMethod(obj interface{}, name string, args ...interface{}) {
v := reflect.ValueOf(obj)
m := v.MethodByName(name)
argsV := make([]reflect.Value, 0, len(args))
for _, arg := range args {
argsV = append(argsV, reflect.ValueOf(arg))
}
rets := m.Call(argsV)
fmt.Println("ret:")
for _, ret := range rets {
fmt.Println(ret.Interface())
}
}
func main() {
m := Math{a: 10, b: 2}
invokeMethod(m, "Add")
invokeMethod(m, "Sub")
invokeMethod(m, "Mul")
invokeMethod(m, "Div")
}
NumMethod()
and Method()
on the reflection value object of the structure to traverse all the methods it defines.
Actual case
Using the method introduced earlier, we can easily implement a simple HTTP-based RPC call. Conventional format: The path name /obj/method/arg1/arg2
calls the obj.method(arg1, arg2)
method.
First define two structures and define methods for them. We agree that exportable methods will be registered as RPC methods. And the method must return two values: a result and an error.
type StringObject struct{}
func (StringObject) Concat(s1, s2 string) (string, error) {
return s1 + s2, nil
}
func (StringObject) ToUpper(s string) (string, error) {
return strings.ToUpper(s), nil
}
func (StringObject) ToLower(s string) (string, error) {
return strings.ToLower(s), nil
}
type MathObject struct{}
func (MathObject) Add(a, b int) (int, error) {
return a + b, nil
}
func (MathObject) Sub(a, b int) (int, error) {
return a - b, nil
}
func (MathObject) Mul(a, b int) (int, error) {
return a * b, nil
}
func (MathObject) Div(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("divided by zero")
}
return a / b, nil
}
Next, we define a structure to indicate the RPC methods that can be called:
type RpcMethod struct {
method reflect.Value
args []reflect.Type
}
Among them, method
is the reflection value object of the method, and args
is the type of each parameter. We define a function to extract methods from the object that can be called by RPC:
var (
mapObjMethods map[string]map[string]RpcMethod
)
func init() {
mapObjMethods = make(map[string]map[string]RpcMethod)
}
func registerMethods(objName string, o interface{}) {
v := reflect.ValueOf(o)
mapObjMethods[objName] = make(map[string]RpcMethod)
for i := 0; i < v.NumMethod(); i++ {
m := v.Method(i)
if m.Type().NumOut() != 2 {
// 排除不是两个返回值的
continue
}
if m.Type().Out(1).Name() != "error" {
// 排除第二个返回值不是 error 的
continue
}
t := v.Type().Method(i)
methodName := t.Name
if len(methodName) <= 1 || strings.ToUpper(methodName[0:1]) != methodName[0:1] {
// 排除非导出方法
continue
}
types := make([]reflect.Type, 0, 1)
for j := 0; j < m.Type().NumIn(); j++ {
types = append(types, m.Type().In(j))
}
mapObjMethods[objName][methodName] = RpcMethod{
m, types,
}
}
}
registerMethods()
function uses reflect.Value.NumMethod()
and reflect.Method(i)
to traverse methods from the object, excluding methods that are not two return values, and the second return value is not error or non-exported.
Then define an http processor:
func handler(w http.ResponseWriter, r *http.Request) {
parts := strings.Split(r.URL.Path[1:], "/")
if len(parts) < 2 {
handleError(w, errors.New("invalid request"))
return
}
m := lookupMethod(parts[0], parts[1])
if m.method.IsZero() {
handleError(w, fmt.Errorf("no such method:%s in object:%s", parts[0], parts[1]))
return
}
argSs := parts[2:]
if len(m.args) != len(argSs) {
handleError(w, errors.New("inconsistant args num"))
return
}
argVs := make([]reflect.Value, 0, 1)
for i, t := range m.args {
switch t.Kind() {
case reflect.Int:
value, _ := strconv.Atoi(argSs[i])
argVs = append(argVs, reflect.ValueOf(value))
case reflect.String:
argVs = append(argVs, reflect.ValueOf(argSs[i]))
default:
handleError(w, fmt.Errorf("invalid arg type:%s", t.Kind()))
return
}
}
ret := m.method.Call(argVs)
err := ret[1].Interface()
if err != nil {
handleError(w, err.(error))
return
}
response(w, ret[0].Interface())
}
We divide the path into a slice, the first element is the object name (that is, math
or string
), the second element is the method name (that is, Add/Sub/Mul/Div
etc.), and the rest are parameters. Next, we look for the method to be called, and convert the string in the path to the corresponding type according to the type of each parameter recorded during registration. Then call, check whether the second return value is nil
to know whether the method call is wrong. A successful call will return the result.
Finally, we only need to start an http server:
func main() {
registerMethods("math", MathObject{})
registerMethods("string", StringObject{})
mux := http.NewServeMux()
mux.HandleFunc("/", handler)
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
if err := server.ListenAndServe(); err != nil {
log.Fatal(err)
}
}
The complete code is in the Github repository. run:
$ go run main.go
Use curl to verify:
$ curl localhost:8080/math/Add/1/2
{"data":3}
$ curl localhost:8080/math/Sub/10/2
{"data":8}
$ curl localhost:8080/math/Div/10/2
{"data":5}
$ curl localhost:8080/math/Div/10/0
{"error":"divided by zero"}
$ curl localhost:8080/string/Concat/abc/def
{"data":"abcdef"}
Of course, this is only a simple implementation, and there are still many error handlings that have not been considered. The type of method parameters currently only supports int
and string
. If you are interested, you can improve it.
Settings
First introduce a concept: addressable. Addressable is the ability to obtain its address through reflection. Addressability is closely related to pointers. All reflect.ValueOf()
obtained through reflect.Value
are not addressable. Because they only save their own value and don't know anything about their address. For example, the pointer p *int
saves the int
data in the memory, but its own address cannot be obtained by itself, because when it is passed to reflect.ValueOf()
, its own address information is lost. We can judge whether it is addressable reflect.Value.CanAddr()
func main() {
x := 2
a := reflect.ValueOf(2)
b := reflect.ValueOf(x)
c := reflect.ValueOf(&x)
fmt.Println(a.CanAddr()) // false
fmt.Println(b.CanAddr()) // false
fmt.Println(c.CanAddr()) // false
}
Although the pointer is not addressable, we can call Elem()
reflect.Value
of the element it points to. This reflect.Value
can be addressed, because it is reflect.Value.Elem()
, and this acquisition path can be recorded. The resulting reflect.Value
contains its address:
d := c.Elem()
fmt.Println(d.CanAddr())
In addition slice reflected by the object Index(i)
obtained by the method reflect.Value
is addressable, we can always get indexed by the address of a slice. The fields obtained through the pointer of the structure are also addressable:
type User struct {
Name string
Age int
}
s := []int{1, 2, 3}
sv := reflect.ValueOf(s)
e := sv.Index(1)
fmt.Println(e.CanAddr()) // true
u := &User{Name: "dj", Age: 18}
uv := reflect.ValueOf(u)
f := uv.Elem().Field(0)
fmt.Println(f.CanAddr()) // true
If a reflect.Value
is addressable, we can call its Addr()
method to return a reflect.Value
, which contains a pointer to the original data. Then call the Interface{}
method on this reflect.Value
, it will return a interface{}
value containing this pointer. If we know the type, we can use type assertions to turn it into an ordinary pointer. To update the value through a normal pointer:
func main() {
x := 2
d := reflect.ValueOf(&x).Elem()
px := d.Addr().Interface().(*int)
*px = 3
fmt.Println(x) // 3
}
Such a method of updating a bit of trouble, we can directly addressable reflect.Value
call Set()
to update the method, not through a pointer:
d.Set(reflect.ValueOf(4))
fmt.Println(x) // 4
If the incoming type does not match, it will panic. reflect.Value
Set
methods for basic types SetInt
, SetUint
, SetFloat
etc.:
d.SetInt(5)
fmt.Println(x) // 5
reflection can read the values of unexported structure fields, but cannot update these values. An addressable reflect.Value
will record whether it is obtained by traversing an unexported field, and if it is, no modification is allowed. Therefore, it is not safe to CanAddr()
determine before the update. CanSet()
can correctly judge whether a value can be modified.
CanSet()
judges the , which is a stricter property than the addressability. If a reflect.Value
is settable, it must be addressable. The opposite is not true:
type User struct {
Name string
age int
}
u := &User{Name: "dj", age: 18}
uv := reflect.ValueOf(u)
name := uv.Elem().Field(0)
fmt.Println(name.CanAddr(), name.CanSet()) // true true
age := uv.Elem().Field(1)
fmt.Println(age.CanAddr(), age.CanSet()) // true false
name.SetString("lidajun")
fmt.Println(u) // &{lidajun 18}
// 报错
// age.SetInt(20)
StructTag
When defining the structure, you can specify a label for each field, and we can use reflection to read these labels:
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
u := &User{Name: "dj", Age: 18}
t := reflect.TypeOf(u).Elem()
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
fmt.Println(f.Tag)
}
}
The label is an ordinary string, the output of the above program:
json:"name"
json:"age"
StructTag
defined in the reflect/type.go
file:
type StructTag string
The general practice is to separate each key-value pair with spaces, and use :
between the key-values. E.g:
`json:"name" xml:"age"`
StructTag
provides the Get()
method to get the value corresponding to the key.
to sum up
This article systematically introduces the reflection mechanism in Go language, from type, interface to reflection usage. It also uses reflection to implement a simple HTTP-based RPC library. Although reflection is not recommended in normal development, you need to frequently use reflection knowledge when reading the source code and writing your own library. Proficiency in reflection can make reading the source code twice the result with half the effort.
If you find a fun and useful Go language library, welcome to submit an issue on the Go Daily Library GitHub😄
reference
- Rob Pike, laws of reflection: https://golang.org/doc/articles/laws_of_reflection.html
- Go Programming Language, Chapter 12: Reflection
- reflect official document, https://pkg.go.dev/reflect
- Go daily one library GitHub: https://github.com/darjun/go-daily-lib
I
My blog: https://darjun.github.io
Welcome to follow my WeChat public account [GoUpUp], learn together and make progress together~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。