GoLang Reflection

Golang Series

Hello GoLang: https://blog.yexca.net/archives/154
GoLang (var and const) Variables and Constants: https://blog.yexca.net/archives/155
GoLang (func) Functions: https://blog.yexca.net/archives/156
GoLang (slice and map) Slices and Maps: https://blog.yexca.net/archives/160
GoLang (OOP) Object-Oriented Programming: https://blog.yexca.net/archives/162
GoLang (reflect) Reflection: This Article
GoLang (struct tag) Struct Tags: https://blog.yexca.net/archives/205
GoLang (goroutine) Goroutines: https://blog.yexca.net/archives/206
GoLang (channel) Channels: https://blog.yexca.net/archives/207


notify:

📢 This article was translated by gemini-3-flash-preview

Reflection refers to a class of applications that are capable of self-description and self-control.

pair

A Go variable consists of a type and a value part, which together form a pair.

image

The static type is the type the programmer sees while coding, while the concrete type is what the runtime system sees.

Whether a type assertion succeeds depends on the variable’s concrete type, not its static type. Therefore, if a read variable’s concrete type also implements a write method, it can be type-asserted as write.

Reflection is built upon types. Since static types are fixed, reflection mainly interacts with the interface type (which holds the concrete type).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import "fmt"

type ReadBook interface {
    Read()
}
type WriteBook interface {
    Write()
}

type Book struct {
}

func (this *Book) Read() {
    fmt.Println("read Book")
}
func (this *Book) Write() {
    fmt.Println("write Book")
}

func main() {
    // b: pair<type:Book, value:address of book{}>
    b := &Book{}

    // r: pair<type:, value:>
    var r ReadBook
    // r: pair<type:Book, value:address of book{}>
    r = b
    r.Read()

    var w WriteBook
    w = r.(WriteBook) // This works because the concrete type of r is Book
    w.Write()
}

TypeOf and ValueOf

reflect.TypeOf() retrieves the type from the pair, and reflect.ValueOf() retrieves the value.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

import (
    "fmt"
    "reflect"
)

func main() {
    a := 3.14
    fmt.Println("type of a:", reflect.TypeOf(a)) // float64
    fmt.Println("value of a:", reflect.ValueOf(a)) // 3.14
}

Type Conversion

Executing reflect.ValueOf() returns a variable of type reflect.Value.

Known Original Data Type

If the original data type is known, you can use a direct type assertion.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import (
    "fmt"
    "reflect"
)

func main() {
    a := 3.14
    fmt.Println("type of a:", reflect.TypeOf(a))
    fmt.Println("value of a:", reflect.ValueOf(a))

    value := reflect.ValueOf(a)
    fmt.Printf("type of value:%T\n", value) // reflect.Value
    newA := value.Interface().(float64)
    fmt.Printf("type of newA:%T\n", newA) // float64
}

Note that type conversion must be an exact match, otherwise it will panic (e.g., when dealing with pointers).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

import (
    "fmt"
    "reflect"
)

func main() {
    a := 3.14
    fmt.Println("type of a:", reflect.TypeOf(a))
    fmt.Println("value of a:", reflect.ValueOf(a))

    // Pass the address
    point := reflect.ValueOf(&a)
    fmt.Printf("type of value:%T\n", point) // reflect.Value
    // Convert type to pointer
    newA := point.Interface().(*float64)
    fmt.Printf("type of newA:%T\n", newA) // *float64
}

In essence, reflection allows you to convert a “reflection type object” back into an “interface type variable”.

Unknown Original Data Type

This is achieved by iterating through fields.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package main

import (
    "fmt"
    "reflect"
)

type Person struct {
    // Each line is a field
    Name string
    Age  int
    Rank float64
}

// Note: Method must be public; private methods are inaccessible
func (person Person) Sleep() {
    fmt.Println("person sleep")
}

func main() {
    p := Person{"zhangSan", 18, 5.2}
    getFieldAndMethod(p)
}

func getFieldAndMethod(input interface{}) {
    getType := reflect.TypeOf(input)
    fmt.Println("type:", getType.Name())

    getValue := reflect.ValueOf(input)
    fmt.Println("value/AllField:", getValue)

    // Get fields
    numField := getValue.NumField()
    for i := 0; i < numField; i++ {
        fieldType := getType.Field(i)
        fieldValue := getValue.Field(i).Interface()
        fmt.Printf("%s: %v = %v\n", fieldType.Name, fieldType.Type, fieldValue)
    }

    // Get methods
    numMethod := getType.NumMethod()
    for i := 0; i < numMethod; i++ {
        method := getType.Method(i)
        fmt.Printf("%s: %v\n", method.Name, method.Type)
    }
}

Assignment via reflect.Value

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
    "fmt"
    "reflect"
)

func main() {
    a := 3.14

    // Assignment is only possible if it's a pointer
    pointer := reflect.ValueOf(&a)
    // Point to the address to get the underlying value
    newValue := pointer.Elem()
    
    // Check if the value can be set
    fmt.Println("value canSet:", newValue.CanSet())
    if newValue.CanSet() {
        // If it can be set
        newValue.SetFloat(9.96)
        // Display variable value
        fmt.Println("value of a:", a) // 9.96
    } else {
        fmt.Println("error")
    }
}

Calling Methods via reflect.Value

Call methods by their name string.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

import (
    "fmt"
    "reflect"
)

type User struct {
    name string
    age  int
}

func (user User) MethodHasArgs(a string, b int) {
    fmt.Println("User MethodHasArgs")
}
func (user User) MethonNotArgs() {
    fmt.Println("User MethodNotArgs")
}

func main() {
    user := User{"zhangSan", 18}
    value := reflect.ValueOf(user)

    // Call by function name
    method1 := value.MethodByName("MethodHasArgs")
    // Construct arguments
    args1 := []reflect.Value{reflect.ValueOf("string"), reflect.ValueOf(18)}
    // Call the function
    method1.Call(args1)

    // Call without arguments
    method2 := value.MethodByName("MethonNotArgs")
    args2 := make([]reflect.Value, 0)
    method2.Call(args2)

}

Basic Principles of Reflection

image