SoFunction
Updated on 2025-03-01

Detailed explanation of the reflection usage of Go language learning notes

This article describes the reflection usage of Go learning notes. Share it for your reference, as follows:

1. Type (Type)

Reflect allows us to detect the type information and memory structure of objects during runtime, which to a certain extent makes up for the lack of dynamic behavior of static languages. At the same time, reflection is also an important means to implement metaprogramming.

Like the C data structure, the Go object header does not have a type pointer, and it is impossible to obtain any type-related information during the runtime. All information required for reflection operation comes fromInterface variables. In addition to storing its own type, interface variables will also save the type data of the actual object.

func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value

These two reflective entry functions will convert any incoming object to interface type.
When facing types, you need to distinguishType andKind. The former represents the real type (static type), and the latter represents its infrastructure (understanding type) category -- base type

Copy the codeThe code is as follows:
type X int
func main() {
    var a X = 100
    t := (a)
    (t)
    ((), ())
}

Output:

X int

Therefore, in terms of type judgment, you must choose the correct way

Copy the codeThe code is as follows:
type X int
type Y int
func main() {
    var a, b X = 100, 200
    var c Y = 300
    ta, tb, tc := (a), (b), (c)
    (ta == tb, ta == tc)
    (() == ())
}

In addition to obtaining types through actual objects, some basic composite types can also be directly constructed.
Copy the codeThe code is as follows:
func main() {
    a := (10, (byte(0)))
    m := ((""), (0))
    (a, m)
}

Output:
[10]uint8   map[string]int

Incoming objects should distinguish between base type and pointer type because they do not belong to the same type.

Copy the codeThe code is as follows:
func main() {
    x := 100
    tx, tp := (x), (&x)
    (tx, tp, tx == tp)
    ((), ())
    (tx == ())
}

Output:

int *int false
int ptr
true

Method Elem() Returns the base type of a pointer, array, slice, dictionary (value), or channel.

Copy the codeThe code is as follows:
func main() {
    ((map[string]int{}).Elem())
    (([]int32{}).Elem())
}

Output:
int
int32

Only after obtaining the base type of the structure pointer can iterate over its fields.

Copy the codeThe code is as follows:

type user struct {
    name string
    age int
}
type manager struct {
    user
    title string
}
func main() {
    var m manager
    t := (&m)
    if () == {
        t = ()
    }
    for i := 0; i < (); i++ {
        f := (i)
        (, , )
if { // Output anonymous field structure
            for x := 0; x < (); x++ {
                af := (x)
                (" ", , )
            }
        }
    }
}

Output:

user  0
 name string
 age int
title string 24

For anonymous fields, they can be accessed directly with multi-level indexes (in the order of definition).

Copy the codeThe code is as follows:
type user struct {
    name string
    age  int
}
type manager struct {
    user
    title string
}
func main() {
    var m manager
    t := (m)
name, _ := ("name") // Find by name
    (, )
age := ([]int{0, 1}) // Find by multi-level index
    (, )
}

Output:

name string
age int

FieldByName() does not support multi-level names. If there is occlusion of the same name, it must be obtained twice through the anonymous field.

Similarly, when outputting the method set, the same distinction isBase typeandPointer type

Copy the codeThe code is as follows:
type A int
type B struct {
    A
}
func (A) av() {}
func (*A) ap() {}
func (B) bv() {}
func (*B) bp() {}
func main() {
    var b B
    t := (&b)
    s := []{t, ()}
    for _, t2 := range s {
        (t2, ":")
        for i := 0; i < (); i++ {
            (" ", (i))
        }
    }
}

Output:

* :
  {ap main func(*) <func(*) Value> 0}
  {av main func(*) <func(*) Value> 1}
  {bp main func(*) <func(*) Value> 2}
  {bv main func(*) <func(*) Value> 3}   
 :
  {av main func(*) <func(*) Value> 0} 
  {bv main func(*) <func(*) Value> 1}

A little different from the imagination, reflection can detect the non-exported structure members of the current package or outsourcing.

Copy the codeThe code is as follows:
import (
    "net/http"
    "reflect"
    "fmt"
)
func main()  {
    var s
    t := (s)
    for i := 0; i < (); i++ {
        ((i).Name)
    }
}

Output:

Addr
Handler
ReadTimeout
WriteTimeout
TLSConfig
MaxHeaderBytes
TLSNextProto
ConnState
ErrorLog
disableKeepAlives
nextProtoOnce
nextProtoErr

Compared with reflect, both packages and outsourcing are "outsourcing".
The struct tag can be extracted using reflection, and it can also be automatically decomposed. It is commonly used in ORM mapping, or data format verification.

Copy the codeThe code is as follows:

type user struct {
    name string `field:"name" type:"varchar(50)"`
    age  int `field:"age" type:"int"`
}
func main() {
    var u user
    t := (u)
    for i := 0; i < (); i++ {
        f := (i)
        ("%s: %s %s\n", , ("field"), ("type"))
    }
}

Output:
name: name varchar(50)
age: age int

Auxiliary judgment methods Implements(), ConvertibleTo, and AssignableTo() are all performed during the runtimeDynamic callandAssignmentRequired.

Copy the codeThe code is as follows:
type X int
func (X) String() string {
    return ""
}
func main()  {
    var a X
    t := (a)
// Implements cannot use type directly as a parameter, which makes this usage very awkward
    st := ((*)(nil)).Elem()
    ((st))
    it := (0)
    ((it))
    ((st), (it))
}

Output:
true
true
true false

2. Value (Value)

Unlike Type to obtain type information, Value focuses on reading and writing object instance data.
As mentioned in the previous chapter, interface variables will copy objects and are unaddressable, so if you want to modify the target object, you must use a pointer.

Copy the codeThe code is as follows:
func main()  {
    a := 100
    va, vp := (a), (&a).Elem()
    ((), ())
    ((), ())
}

Output:
false false
true true

Even if the pointer is passed, it still needs to be passedElem()Get the target object. Because the pointer stored by the interface cannot be addressed and set up operations.
Notice, no direct setting operations for non-exported fields, whether it is the current package or outsourcing.

Copy the codeThe code is as follows:
type User struct {
    Name string
    code int
}
func main() {
    p := new(User)
    v := (p).Elem()
    name := ("Name")
    code := ("code")
    ("name: canaddr = %v, canset = %v\n", (), ())
    ("code: canaddr = %v, canset = %v\n", (), ())
    if () {
        ("Tom")
    }
    if () {
        *(*int)((())) = 100
    }
    ("%+v\n", *p)
}

Output:
name: canaddr = true, canset = true
code: canaddr = true, canset = false
{Name:Tom code:100}

Method types such as and convert the stored data into a pointer, and the target must be a pointer type. UnsafeAddr returns any CanAddr address (equivalent to & address fetching operation), such as the Value after Elem() and the field member address.

Taking the pointer type field in the structure as an example, Pointer returns the address saved by the field, and UnsafeAddr returns the address of the field itself (structural object address + offset).
Type recommendations and conversions can be performed through the Interface method.

Copy the codeThe code is as follows:
func main() {
    type user struct {
        Name string
        Age  int
    }
    u := user{
        "",
        60,
    }
    v := (&u)
    if !() {
        println("CanInterface: fail.")
        return
    }
    p, ok := ().(*user)
    if !ok {
        println("Interface: fail.")
        return
    }
    ++
    ("%+v\n", u)
}

Output:

{Name: Age:61}

You can also use , Bool and other methods to perform type conversion directly, but pani will be triggered when it fails, and ok-iiom ​​is not supported.

Example of compound type object settings:

Copy the codeThe code is as follows:
func main()  {
    c := make(chan int, 4)
    v := (c)
    if ((100)) {
        (())
    }
}

Output:
100 true

The interface has two nil states, which has always been a potential trouble. The solution is to use IsNil() to determine whether the value is nil.

Copy the codeThe code is as follows:
func main()  {
    var a interface{} = nil
    var b interface{} = (*int)(nil)
    (a == nil)
    (b == nil, (b).IsNil())
}

Output:
true
false true

You can also directly determine whether it is a zero value after conversion by unsafe.

Copy the codeThe code is as follows:
func main()  {
    var b interface{} = (*int)(nil)
    iface := (*[2]uintptr)((&b))
    (iface, iface[1] == 0)
}

Output:
&[712160 0] true

What makes people feel helpless is that some methods in Value do not implement ok-idom or return error, so you have to judge whether the returned Zero Value is.

Copy the codeThe code is as follows:
func main()  {
    v := (struct {name string}{})
    println(("name").IsValid())
    println(("xxx").IsValid())
}

Output:

true
false

3. Method

Dynamically calling methods is not too troublesome. Just press the In list to prepare the required parameters.

Copy the codeThe code is as follows:
type X struct {}
func (X) Test(x, y int) (int, error)  {
    return x + y, ("err: %d", x + y)
}
func main()  {
    var a X
    v := (&a)
    m := ("Test")
    in := []{
        (1),
        (2),
    }
    out := (in)
    for _, v := range out {
        (v)
    }
}

Output:

3
err: 3

For variable parameters, using CallSlice() is more convenient.

Copy the codeThe code is as follows:
type X struct {}
func (X) Format(s string, a ...interface{}) string {
    return (s, a...)
}
func main() {
    var a X
    v := (&a)
    m := ("Format")
    out := ([]{
("%s = %d"), // All parameters must be processed
        ("x"),
        (100),
    })
    (out)
    out = ([]{
        ("%s = %d"),
        ([]interface{}{"x", 100}),
    })
    (out)
}

Output:

[x = 100]
[x = 100]

The non-export method cannot be called, and even the valid address cannot be obtained.

4. Construction

The reflection library provides built-in functionsmake()andnew()The most interesting one isMakeFunc(). It can be used to implement a common template to adapt to different data types.

Copy the codeThe code is as follows:
// General algorithm functions
func add(args []) (results []) {
    if len(args) == 0 {
        return nil
    }
    var ret
    switch args[0].Kind() {
    case :
        n := 0
        for _, a := range args {
            n += int(())
        }
        ret = (n)
    case :
        ss := make([]string, 0, len(args))
        for _, s := range args {
            ss = append(ss, ())
        }
        ret = ((ss, ""))
    }
    results = append(results, ret)
    return
}
// Point the function pointer parameter to a general algorithm function
func makeAdd(fptr interface{}) {
    fn := (fptr).Elem()
v := ((), add) // This is the key
(v)
}
func main() {
    var intAdd func(x, y int) int
    var strAdd func(a, b string) string
    makeAdd(&intAdd)
    makeAdd(&strAdd)
    println(intAdd(100, 200))
    println(strAdd("hello, ", "world!"))
}

Output:

300
hello, world!

If the language supports generics, it is natural that there is no need to worry about it like this

I hope this article will be helpful to everyone's Go language programming.