Reflection
Reflection is a mechanism that allows inspecting the structure of a language itself at runtime. It can flexibly handle certain problems, but it also has obvious drawbacks, such as performance issues. In Go, reflection is closely related to interface{}. To a large extent, wherever interface{} appears, there will be reflection. The reflection API in Go is provided by the standard library reflect package.
Interfaces
Before we begin, let's briefly understand two interfaces in the runtime package. In Go, interfaces are essentially structs. Go divides interfaces into two major categories at runtime: interfaces without method sets and interfaces with method sets. For interfaces with method sets, they are represented by the following iface struct at runtime:
type iface struct {
tab *itab // contains data type, interface type, method set, etc.
data unsafe.Pointer // pointer to the value
}For interfaces without method sets, they are represented by the eface struct at runtime, as follows:
type eface struct {
_type *_type // type
data unsafe.Pointer // pointer to the value
}Both of these structs have corresponding struct types in the reflect package. iface corresponds to nonEmptyInterface:
type nonEmptyInterface struct {
itab *struct {
ityp *rtype // static interface type
typ *rtype // dynamic concrete type
hash uint32 // type hash
_ [4]byte
fun [100000]unsafe.Pointer // method set
}
word unsafe.Pointer // pointer to the value
}And eface corresponds to emptyInterface:
type emptyInterface struct {
typ *rtype // dynamic concrete type
word unsafe.Pointer // value pointing to a pointer
}For these two types, the official documentation provides clear definitions:
nonEmptyInterface: nonEmptyInterface is the header for an interface value with methodsemptyInterface: emptyInterface is the header for an interface{} value
The term "dynamic concrete type" mentioned above (original: dynamic concrete type) refers to the fact that Go is a 100% statically typed language. The word "static" is reflected in the abstract interface type that remains unchanged externally, while "dynamic" indicates that the concrete implementation type stored internally can vary. At this point, understanding the simple principles of interfaces to this extent is sufficient for subsequent learning about reflection.
Bridge
In the reflect package, there are reflect.Type interface type to represent types in Go, and reflect.Value struct type to represent values in Go:
type Type interface {
...
Name() string
PkgPath() string
Size() uintptr
String() string
Kind() Kind
...
}
type Value struct {
typ *rtype
ptr unsafe.Pointer
flag
}The code above omits many details. For now, just understand the existence of these two types. All reflection-related operations in Go are based on these two types. The reflect package provides two functions to convert Go types into the above two types for reflection operations: reflect.TypeOf function:
func TypeOf(i any) TypeAnd reflect.ValueOf function:
func ValueOf(i any) ValueAs you can see, both functions take parameters of type any, which is an alias for interface{}. If you want to perform reflection operations, you need to convert the type to interface{} first. This is why, as mentioned earlier, reflection cannot exist without the empty interface. Loosely speaking, the empty interface is the bridge connecting Go's type system with reflection. The following diagram vividly describes this process.

TIP
In the following text, for convenience, we will uniformly use the alias any to replace interface{}
Core
There are three classic reflection laws in Go. Combined with the content above, they are easy to understand:
Reflection can convert variables of
interface{}type into reflection objectsReflection can restore reflection objects back to
interface{}type variablesTo modify a reflection object, its value must be settable
These three laws are the core of Go reflection. When you need to access type-related information, you use reflect.TypeOf. When you need to modify reflection values, you use reflect.ValueOf.
Types
reflect.Type represents types in Go. You can use the reflect.TypeOf() function to convert a variable to reflect.Type. Code example:
func main() {
str := "hello world!"
reflectType := reflect.TypeOf(str)
fmt.Println(reflectType)
}Output:
stringKind
For Type, Go internally uses reflect.Kind to represent basic types in Go. It is essentially an unsigned integer uint.
type Kind uintThe reflect package uses Kind to enumerate all basic types in Go, as shown below:
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
Pointer
Slice
String
Struct
UnsafePointer
)The Kind type only implements the String() method of the Stringer interface. This type has only this one method. The return value of the String() method comes from an internal slice, as shown below. This syntax may look like a map at first glance, but it's actually a special syntax in Go: index expressions in slice literals.
var kindNames = []string{
Invalid: "invalid",
Bool: "bool",
Int: "int",
Int8: "int8",
Int16: "int16",
Int32: "int32",
Int64: "int64",
Uint: "uint",
Uint8: "uint8",
Uint16: "uint16",
Uint32: "uint32",
Uint64: "uint64",
Uintptr: "uintptr",
Float32: "float32",
Float64: "float64",
Complex64: "complex64",
Complex128: "complex128",
Array: "array",
Chan: "chan",
Func: "func",
Interface: "interface",
Map: "map",
Pointer: "ptr",
Slice: "slice",
String: "string",
Struct: "struct",
UnsafePointer: "unsafe.Pointer",
}type Type interface{
Kind() Kind
}Through Kind, you can know what basic type the value stored in the empty interface actually is. For example:
func main() {
// Declare a variable of type any
var eface any
// Assign a value
eface = 100
// Get its type through the Kind method
fmt.Println(reflect.TypeOf(eface).Kind())
}Output:
intElem
type Type interface{
Elem() Type
}Using the Type.Elem() method, you can determine the element type stored in a data structure of type any. The acceptable underlying parameter types must be one of: pointer, slice, array, channel, or map. Otherwise, it will panic. Here's a code example:
func main() {
var eface any
eface = map[string]int{}
rType := reflect.TypeOf(eface)
// Key() returns the reflection type of the map's key
fmt.Println(rType.Key().Kind())
fmt.Println(rType.Elem().Kind())
}Output:
string
intA pointer can also be understood as a container. Using Elem() on a pointer will obtain the reflection type of the element it points to. Code example:
func main() {
var eface any
// Assign a pointer
eface = new(strings.Builder)
rType := reflect.TypeOf(eface)
// Get the reflection type of the element the pointer points to
vType := rType.Elem()
// Output the package path
fmt.Println(vType.PkgPath())
// Output its name
fmt.Println(vType.Name())
}strings
BuilderUsing arrays, slices, and channels works similarly.
Size
type Type interface{
Size() uintptr
}The Size method can be used to get the byte size occupied by the corresponding type. Example:
func main() {
fmt.Println(reflect.TypeOf(0).Size())
fmt.Println(reflect.TypeOf("").Size())
fmt.Println(reflect.TypeOf(complex(0, 0)).Size())
fmt.Println(reflect.TypeOf(0.1).Size())
fmt.Println(reflect.TypeOf([]string{}).Size())
}Output:
8
16
16
8
24TIP
Using unsafe.Sizeof() can achieve the same effect.
Comparable
type Type interface{
Comparable() bool
}The Comparable method can determine whether a type can be compared. Example:
func main() {
fmt.Println(reflect.TypeOf("hello world!").Comparable())
fmt.Println(reflect.TypeOf(1024).Comparable())
fmt.Println(reflect.TypeOf([]int{}).Comparable())
fmt.Println(reflect.TypeOf(struct{}{}).Comparable())
}Output:
true
true
false
trueImplements
type Type interface{
Implements(u Type) bool
}The Implements method can determine whether a type implements a certain interface:
type MyInterface interface {
My() string
}
type MyStruct struct {
}
func (m MyStruct) My() string {
return "my"
}
type HisStruct struct {
}
func (h HisStruct) String() string {
return "his"
}
func main() {
rIface := reflect.TypeOf(new(MyInterface)).Elem()
fmt.Println(reflect.TypeOf(new(MyStruct)).Elem().Implements(rIface))
fmt.Println(reflect.TypeOf(new(HisStruct)).Elem().Implements(rIface))
}Output:
true
falseConvertibleTo
type Type interface{
ConvertibleTo(u Type) bool
}The ConvertibleTo method can determine whether a type can be converted to another specified type:
type MyInterface interface {
My() string
}
type MyStruct struct {
}
func (m MyStruct) My() string {
return "my"
}
type HisStruct struct {
}
func (h HisStruct) String() string {
return "his"
}
func main() {
rIface := reflect.TypeOf(new(MyInterface)).Elem()
fmt.Println(reflect.TypeOf(new(MyStruct)).Elem().ConvertibleTo(rIface))
fmt.Println(reflect.TypeOf(new(HisStruct)).Elem().ConvertibleTo(rIface))
}Output:
true
falseValues
reflect.Value represents the value of a reflection interface. You can use the reflect.ValueOf() function to convert a variable to reflect.Value. Code example:
func main() {
str := "hello world!"
reflectValue := reflect.ValueOf(str)
fmt.Println(reflectValue)
}Output:
hello world!Type
func (v Value) Type() TypeThe Type method can get the type of a reflection value:
func main() {
num := 114514
rValue := reflect.ValueOf(num)
fmt.Println(rValue.Type())
}Output:
intElem
func (v Value) Elem() ValueGet the element reflection value of a reflection value:
func main() {
num := new(int)
*num = 114514
// Take a pointer as an example
rValue := reflect.ValueOf(num).Elem()
fmt.Println(rValue.Interface())
}Output:
114514Pointers
There are two ways to get the pointer of a reflection value:
// Returns a pointer reflection value representing the address of v
func (v Value) Addr() Value
// Returns a uintptr pointing to the raw value of v, equivalent to uintptr(Value.Addr().UnsafePointer())
func (v Value) UnsafeAddr() uintptr
// Returns a uintptr pointing to the raw value of v
// Only when v's Kind is Chan, Func, Map, Pointer, Slice, UnsafePointer, otherwise panic
func (v Value) Pointer() uintptr
// Returns an unsafe.Pointer pointing to the raw value of v
// Only when v's Kind is Chan, Func, Map, Pointer, Slice, UnsafePointer, otherwise panic
func (v Value) UnsafePointer() unsafe.PointerExample:
func main() {
num := 1024
ele := reflect.ValueOf(&num).Elem()
fmt.Println("&num", &num)
fmt.Println("Addr", ele.Addr())
fmt.Println("UnsafeAddr", unsafe.Pointer(ele.UnsafeAddr()))
fmt.Println("Pointer", unsafe.Pointer(ele.Addr().Pointer()))
fmt.Println("UnsafePointer", ele.Addr().UnsafePointer())
}Output:
&num 0xc0000a6058
Addr 0xc0000a6058
UnsafeAddr 0xc0000a6058
Pointer 0xc0000a6058
UnsafePointer 0xc0000a6058TIP
fmt.Println will reflect to get the type of the parameter. If it's a reflect.Value type, it will automatically call Value.Interface() to get its original value.
Let's try it again with a map:
func main() {
dic := map[string]int{}
ele := reflect.ValueOf(&dic).Elem()
println(dic)
fmt.Println("Addr", ele.Addr())
fmt.Println("UnsafeAddr", *(*unsafe.Pointer)(unsafe.Pointer(ele.UnsafeAddr())))
fmt.Println("Pointer", unsafe.Pointer(ele.Pointer()))
fmt.Println("UnsafePointer", ele.UnsafePointer())
}Output:
0xc00010e4b0
Addr &map[]
UnsafeAddr 0xc00010e4b0
Pointer 0xc00010e4b0
UnsafePointer 0xc00010e4b0Setting Values
func (v Value) Set(x Value)If you want to modify a reflection value through reflection, its value must be addressable. In this case, you should modify its element value through a pointer, rather than directly trying to modify the element's value.
func main() {
// *int
num := new(int)
*num = 114514
rValue := reflect.ValueOf(num)
// Get the element the pointer points to
ele := rValue.Elem()
fmt.Println(ele.Interface())
ele.SetInt(11)
fmt.Println(ele.Interface())
}Output:
114514
11Getting Values
func (v Value) Interface() (i any)You can get the original value of a reflection value through the Interface() method:
func main() {
var str string
str = "hello"
rValue := reflect.ValueOf(str)
if v, ok := rValue.Interface().(string); ok {
fmt.Println(v)
}
}Output:
helloFunctions
Through reflection, you can get all information about a function, and you can also call a function via reflection.
Information
Get all information about a function through reflection type:
func Max(a, b int) int {
if a > b {
return a
}
return b
}
func main() {
rType := reflect.TypeOf(Max)
// Output function name; literal function types don't have a name
fmt.Println(rType.Name())
// Output the number of parameters and return values
fmt.Println(rType.NumIn(), rType.NumOut())
rParamType := rType.In(0)
// Output the type of the first parameter
fmt.Println(rParamType.Kind())
rResType := rType.Out(0)
// Output the type of the first return value
fmt.Println(rResType.Kind())
}Output:
2 1
int
intCalling
Call a function through a reflection value:
func (v Value) Call(in []Value) []Valuefunc main() {
// Get the reflection value of the function
rType := reflect.ValueOf(Max)
// Pass in the parameter array
rResValue := rType.Call([]reflect.Value{reflect.ValueOf(18), reflect.ValueOf(50)})
for _, value := range rResValue {
fmt.Println(value.Interface())
}
}Output:
50Structs
Suppose we have the following struct:
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
money int
}
func (p Person) Talk(msg string) string {
return msg
}Accessing Fields
The reflect.StructField struct is structured as follows:
type StructField struct {
// Field name
Name string
// Package name
PkgPath string
// Type name
Type Type
// Tag
Tag StructTag
// Byte offset of the field
Offset uintptr
// Index
Index []int
// Whether it's an embedded field
Anonymous bool
}There are two ways to access struct fields: one is by index, and the other is by name.
type Type interface{
Field(i int) StructField
}Example of accessing by index:
func main() {
rType := reflect.TypeOf(new(Person)).Elem()
// Output the number of struct fields
fmt.Println(rType.NumField())
for i := 0; i < rType.NumField(); i++ {
structField := rType.Field(i)
fmt.Println(structField.Index, structField.Name, structField.Type, structField.Offset, structField.IsExported())
}
}Output:
4
[0] Name string 0 true
[1] Age int 16 true
[2] Address string 24 true
[3] money int 40 falsetype Type interface{
FieldByName(name string) (StructField, bool)
}Example of accessing by name:
func main() {
rType := reflect.TypeOf(new(Person)).Elem()
// Output the number of struct fields
fmt.Println(rType.NumField())
if field, ok := rType.FieldByName("money"); ok {
fmt.Println(field.Name, field.Type, field.IsExported())
}
}Output:
4
money int falseModifying Fields
If you want to modify a struct field value, you must pass in a struct pointer. Here's an example of modifying a field:
func main() {
// Pass in a pointer
rValue := reflect.ValueOf(&Person{
Name: "",
Age: 0,
Address: "",
money: 0,
}).Elem()
// Get the field
name := rValue.FieldByName("Name")
// Modify the field value
if (name != reflect.Value{}) { // If reflect.Value{} is returned, the field doesn't exist
name.SetString("jack")
}
// Output the struct
fmt.Println(rValue.Interface())
}Output:
{jack 0 0}For modifying private struct fields, some additional operations are required:
func main() {
// Pass in a pointer
rValue := reflect.ValueOf(&Person{
Name: "",
Age: 0,
Address: "",
money: 0,
}).Elem()
// Get a private field
money := rValue.FieldByName("money")
// Modify the field value
if (money != reflect.Value{}) {
// Construct a pointer reflection value pointing to the unexported field of the struct
p := reflect.NewAt(money.Type(), money.Addr().UnsafePointer())
// Get the element the pointer points to, which is the field to be modified
field := p.Elem()
// Modify the value
field.SetInt(164)
}
// Output the struct
fmt.Printf("%+v\n", rValue.Interface())
}Accessing Tags
After obtaining StructField, you can directly access its Tag:
// If it doesn't exist, ok is false
func (tag StructTag) Lookup(key string) (value string, ok bool)
// If it doesn't exist, returns an empty string
func (tag StructTag) Get(key string) stringExample:
func main() {
rType := reflect.TypeOf(new(Person)).Elem()
name, ok := rType.FieldByName("Name")
if ok {
fmt.Println(name.Tag.Lookup("json"))
fmt.Println(name.Tag.Get("json"))
}
}Output:
name true
nameAccessing Methods
Accessing methods is very similar to accessing fields, except the function signatures are slightly different. The reflect.Method struct is as follows:
type Method struct {
// Method name
Name string
// Package name
PkgPath string
// Method type
Type Type
// The function corresponding to the method; the first parameter is the receiver
Func Value
// Index
Index int
}Example of accessing method information:
func main() {
// Get the struct reflection type
rType := reflect.TypeOf(new(Person)).Elem()
// Output the number of methods
fmt.Println(rType.NumMethod())
// Iterate and output method information
for i := 0; i < rType.NumMethod(); i++ {
method := rType.Method(i)
fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
}
}Output:
1
0 Talk func(main.Person, string) string trueIf you want to get details about method parameters and return values, you can obtain them through Method.Func. The process is the same as accessing function information. Modifying the code above slightly:
func main() {
// Get the struct reflection type
rType := reflect.TypeOf(new(Person)).Elem()
// Output the number of methods
fmt.Println(rType.NumMethod())
// Iterate and output method information
for i := 0; i < rType.NumMethod(); i++ {
method := rType.Method(i)
fmt.Println(method.Index, method.Name, method.Type, method.IsExported())
fmt.Println("Method parameters")
for i := 0; i < method.Func.Type().NumIn(); i++ {
fmt.Println(method.Func.Type().In(i).String())
}
fmt.Println("Method return values")
for i := 0; i < method.Func.Type().NumOut(); i++ {
fmt.Println(method.Func.Type().Out(i).String())
}
}
}You can see that the first parameter is main.Person, which is the receiver type:
1
0 Talk func(main.Person, string) string true
Method parameters
main.Person
string
Method return values
stringCalling Methods
Calling a method is similar to calling a function, and you don't need to manually pass in the receiver. Example:
func main() {
// Get the struct reflection type
rValue := reflect.ValueOf(new(Person)).Elem()
// Output the number of methods
fmt.Println(rValue.NumMethod())
// Iterate and output method information
talk := rValue.MethodByName("Talk")
if (talk != reflect.Value{}) {
// Call the method and get the return value
res := talk.Call([]reflect.Value{reflect.ValueOf("hello,reflect!")})
// Iterate and output return values
for _, re := range res {
fmt.Println(re.Interface())
}
}
}Output:
1
hello,reflect!Creation
You can construct new values through reflection. The reflect package provides different convenient functions based on some special types.
Basic Types
// Returns a pointer reflection value pointing to the reflection value
func New(typ Type) ValueUsing string as an example:
func main() {
rValue := reflect.New(reflect.TypeOf(*new(string)))
rValue.Elem().SetString("hello world!")
fmt.Println(rValue.Elem().Interface())
}hello world!Structs
Struct creation also uses the reflect.New function:
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Address string `json:"address"`
money int
}
func (p Person) Talk(msg string) string {
return msg
}
func main() {
// Create struct reflection value
rType := reflect.TypeOf(new(Person)).Elem()
person := reflect.New(rType).Elem()
fmt.Println(person.Interface())
}Output:
{ 0 0}Slices
Create slices via reflection:
func MakeSlice(typ Type, len, cap int) Valuefunc main() {
// Create slice reflection value
rValue := reflect.MakeSlice(reflect.TypeOf(*new([]int)), 10, 10)
// Iterate and assign values
for i := 0; i < 10; i++ {
rValue.Index(i).SetInt(int64(i))
}
fmt.Println(rValue.Interface())
}[0 1 2 3 4 5 6 7 8 9]Map
Create maps via reflection:
func MakeMapWithSize(typ Type, n int) Valuefunc main() {
// Build map reflection value
rValue := reflect.MakeMapWithSize(reflect.TypeOf(*new(map[string]int)), 10)
// Set values
rValue.SetMapIndex(reflect.ValueOf("a"), reflect.ValueOf(1))
fmt.Println(rValue.Interface())
}map[a:1]Channels
Create channels via reflection:
func MakeChan(typ Type, buffer int) Valuefunc main() {
// Create channel reflection value
makeChan := reflect.MakeChan(reflect.TypeOf(new(chan int)).Elem(), 0)
fmt.Println(makeChan.Interface())
}Functions
Create functions via reflection:
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Valuefunc main() {
// Pass in the wrapper type and function body
fn := reflect.MakeFunc(reflect.TypeOf(new(func(int))).Elem(), func(args []reflect.Value) (results []reflect.Value) {
for _, arg := range args {
fmt.Println(arg.Interface())
}
return nil
})
fmt.Println(fn.Type())
fn.Call([]reflect.Value{reflect.ValueOf(1024)})
}Output:
func(int)
1024Deep Equality
reflect.DeepEqual is a function provided by the reflection package to determine whether two variables are completely equal. The signature is as follows:
func DeepEqual(x, y any) boolThis function handles each basic type. Here are some type judgment methods:
- Arrays: Every element in the array is completely equal
- Slices: When both are
nil, they are judged as completely equal; or when both are non-empty, elements within the length range are completely equal - Structs: All fields are completely equal
- Maps: When both are
nil, they are completely equal; when both are non-nil, values mapped by each key are completely equal - Pointers: Point to the same element or point to completely equal elements
- Interfaces: When the concrete types of the interfaces are completely equal
- Functions: Only when both are
nilare they completely equal; otherwise, they are not completely equal
Here are some examples:
Slices
func main() {
a := make([]int, 100)
b := make([]int, 100)
fmt.Println(reflect.DeepEqual(a, b))
}Output:
trueStructs
func main() {
mike := Person{
Name: "mike",
Age: 39,
Father: nil,
}
jack := Person{
Name: "jack",
Age: 18,
Father: &mike,
}
tom := Person{
Name: "tom",
Age: 18,
Father: &mike,
}
fmt.Println(reflect.DeepEqual(mike, jack))
fmt.Println(reflect.DeepEqual(tom, jack))
fmt.Println(reflect.DeepEqual(jack, jack))
}Output:
false
false
true