English original address: blog.golang.org/laws-of-ref…

Introduction to the

Reflection in a computer is the ability of a program to examine its own structure, especially type. It’s a form of metaprogramming, and one of the most confusing.

Types and interfaces

Since reflection is built on top of the type system, let’s start with the basics of types. Go is a statically typed language. Each variable has a static type, which is determined at compile time. For example, int, float32, *MyType, []byte, etc. We make the following declaration:

type MyInt int

var i int
var j MyInt
Copy the code

The above code has variables I of type int and j of type MyInt. Although the variables I and j share the same underlying type int, compiling directly from assignment to each other without a cast will report an error

An important category of types is interface types, which are fixed collections of methods. Interface variables can store any type of concrete (non-interface) value, as long as that value implements all the methods of the interface. A typical example is IO.Reader and IO.Writer, which are the Reader and Writer types in the IO package:

// Reader is the interface that wraps the basic Read method.
type Reader interface {
 Read(p []byte) (n int, err error)
}

// Writer is the interface that wraps the basic Write method.
type Writer interface {
 Write(p []byte) (n int, err error)
}
Copy the code

Any type that implements a Read or Write method can be said to implement the IO.Reader or IO.Writer interface. This means that a variable of type IO.Reader can hold (or point to) any value that has a Read method:

var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
// and so on
Copy the code

The thing to keep in mind is that no matter what value r points to, it’s always of type io.reader. Remember: Go is statically typed, and the static type of r is IO.Reader.

A particularly important interface type is the null interface:

interface{}
Copy the code

An empty interface represents an empty collection of methods, because a value of any type can have zero or more methods, so a variable of type interface{} can store any value.

Some say Go’s interface is dynamically typed. That’s wrong! Interface variables are also statically typed; they always have the same static type. If the value it stores changes at run time, that value must also implement a collection of methods of the interface type.

Since reflection and interfaces are closely related, we must clarify this point.

Representation of interface variables

Russ Cox wrote a detailed article on interface variable representation in Go. Here is only to change the article to do a simple summary:

Variables of interface type store a pair of values: the specific value assigned to the variable and the type descriptor for that value. More precisely, the value is the underlying data that implements the interface, and the type is a description of the underlying data type

Here’s an example:

var r io.Reader
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
iferr ! =nil {
    return nil, err
}
r = tty
Copy the code

In the above example r contains a (value, type) pair :(tty, * os.file). Note that the os.File type does not only implement the Read method; Even though this interface variable only provides access to the Read method, the underlying value contains all type information about the value. So we can do the following type conversion:

var w io.Writer
w = r.(io.Writer)
Copy the code

The second line of the code above is a type assertion: it concludes that the actual value inside the variable r also implements the IO.Writer interface so that it can be assigned to W. After the assignment, w points to the (tty, * os.file) pair, which is the same (value, type) pair that r points to. Interface variables can only call specific methods because of static typing constraints on interfaces, even though the underlying values have many methods.

Let’s read on:

var empty interface{}
empty = w
Copy the code

The empty interface variable empty also contains (tty, * os.file) pairs. This is easy to understand: an empty interface variable can store any specific value and all the description of that value. We do not use type assertions here because the variable W satisfies all methods of the empty interface. In the previous example, an explicit type assertion is required to convert a specific value from IO.Reader to IO.Writer because the set of IO.

It is important to note that the type in a pair (value, type) must be a concrete type (struct or primitive type), not an interface type. The interface type cannot store interface variables.

Three laws of reflection

First law of reflection

Reflection goes from interface Value to Reflection Object can convert “interface type variable” to “Reflection type object”

In terms of usage, reflection provides a mechanism that allows a program to examine, at run time, pairs of (value, type) stored inside interface variables. To start, let’s look at the two types of reflect packages: Type and Value. These two types make it possible to access data within an interface. They correspond to two simple methods, reflect.typeof and Reflect.valueof, which are used to read the reflect.type and reflect.value parts of the interface variable, respectively. Of course, reflect.Type is also easy to get from reflect.Value. So let’s just separate them for now.

Let’s take a look at reflect.TypeOf:

package main

import (
    "fmt"
    "reflect"
)

func main(a) {
    var x float64 = 3.4
    fmt.Println("type:", reflect.TypeOf(x))
}
Copy the code

This code will print:

type: float64
Copy the code

You might be wondering where the interface is because the program looks like it’s passing the float64 variable X instead of the interface value to reflect it. TypeOf. But there, when the godoc reports, the reflect.typeof signature includes an empty interface:

You may be wondering: Why didn’t you see the interface? This code looks like it just passes a variable x of type float64 to Reflect. TypeOf, without passing the interface. In fact, the interface is there. If you look at the TypeOf documentation, you’ll see that reflect.TypeOf’s function signature contains an empty interface:

// TypeOf returns the reflection Type of the value in the interface{}.
func TypeOf(i interface{}) Type
Copy the code

When we call reflect.typeof (x), x is stored in an empty interface variable and passed; TypeOf then disassembles the empty interface variable and restores its type information.

The reflect.valueof function also restores the underlying value (we’ll ignore the details here and focus on the executable code) :

When we call reflect.typeof (x), x is first stored in an empty interface variable and then passed as a parameter; TypeOf then unpacks (unpacks) the empty interface variable to restore its type information.

Of course reflect.ValueOf can restore the underlying value:

Var x float64 = 3.4 ftt.println ("value:", reflect.valueof (x).string ())Copy the code

The code above is printed:

value: <float64 Value>
Copy the code

The String method is explicitly called in the above code because, by default, the FMT package will dig into reflect.value to display the specific Value in it. The String method returns a String.

The types reflect.Type and reflect.Value have a number of methods that we can check and use. Here are a few examples.

Both reflect.Type and reflect.Value have a number of methods that let us examine and manipulate them. The Type reflect.value has a method Type() that returns an object of Type reflect.type. Both Type and Value have a method called Kind, which returns a constant representing the underlying data Type. Common values include Uint, Float64, Slice, and so on. The Value type also has methods like Int and Float to extract the underlying data. The Int method is used to extract INT64 and the Float method is used to extract float64.

var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())
Copy the code

The code above is printed:

Type: float64 Kind is float64: True Value: 3.4Copy the code

Before discussing other methods of modifying data, such as SetInt and SetFloat, we need to understand settability, which is explained in detail in the Third Law of Reflection.

The reflection library provides a number of properties worth listing separately. I’ll start with the getter and setter methods for Value. To keep the API concise, these two methods operate on the widest range of types in a given set. For example, for any signed integer, use INT64. This means that the Value Int returns an int64 Value and the SetInt method receives an int64 parameter. In practice, you may need to convert to the actual type:

var x uint8 = 'x'
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())                            // uint8.
fmt.Println("kind is uint8: ", v.Kind() == reflect.Uint8) // true.
x = uint8(v.Uint())                                       // v.Uint returns a uint64.
Copy the code

The second property is that the Kind method of reflection Objects returns the type of the underlying data, rather than the static type. If a reflection type object contains a user-defined integer:

type MyInt int
var x MyInt = 7
v := reflect.ValueOf(x)
Copy the code

In the above code, the Kind method still returns reflect.int, even though the static type of v is MyInt, not int. In other words, the Kind method does not distinguish between MyInt and int the way the Type method does.

Second law of reflection

Reflection goes from Reflection Object to Interface Value Reflection can convert “Reflection type object” to “interface type variable”

Like physical reflections, reflections in Go generate their own inversions.

Like physical reflection, reflection in Go can create objects of its own reverse type.

Given a variable of type reflect.Value, we can use the Interface method to restore the Value of its Interface type. In effect, this method packages and populates the type and value information into an interface variable and returns it.

// Interface returns v's value as an interface{}.
func (v Value) Interface(a) interface{}
Copy the code

We can then restore the underlying concrete value with assertions:

y := v.Interface().(float64) // y will have type float64.
fmt.Println(y)
Copy the code

The above code prints a value of type FLOAT64, which is represented by the reflection type variable V.

In fact, we can make better use of this feature. Functions such as fmt.Println and fmt.Printf in the standard library take empty interface variables as arguments, which are unpacked inside the FMT package (we did the same thing in the previous example). Thus, the print function of the FMT package only needs to pass the result of the Interface method to the formatter when it prints the reflect.Value variable:

fmt.Println(v.Interface())
Copy the code

Why not just print v, such as FMT.Println(v)? The answer is that v is of type reflect.Value, and what we need is the specific Value it stores. Since the underlying value is a float64, we can format the print:

fmt.Printf("Value is 7.1 e \ % n", v.Interface())
Copy the code

Print out the code above:

The value is 3.4 e+00Copy the code

Again, no type assertion is required on the result of v.interface (). The empty interface value contains the type information of the specific value, and the Printf function restores the type information.

In simple terms, the Interface method does the opposite of the ValueOf function, except that the static type of its return value is Interface {}.

To reiterate: Go’s reflection mechanism converts “variables of interface type” to “objects of reflection type”, and then converts “objects of reflection type” to the past.

Third law of reflection

To modify a Reflection object, the value must be settable. To modify a Reflection object, the value must be settable

The third law is the most subtle and confusing, but it’s easy to understand if we start with the first principle.

The following code does not work, but is well worth investigating:

var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic.
Copy the code

If you run this code, it throws throws a strange exception:

panic: reflect.Value.SetFloat using unaddressable value
Copy the code

The problem here is not that the value 7.1 cannot be addressable, but that the variable v is “unwritable”. Writability is a property of reflection type variables, but not all reflection type variables have this property.

We can check the writability of a reflect.Value variable using the CanSet method:

var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("settability of v:", v.CanSet())
Copy the code

The code above prints:

settability of v: false
Copy the code

For a variable of type Value that is not “writable”, calling the Set method returns an error. First, we need to figure out what “writability” is.

“Writability” is somewhat similar to addressing capability, but more rigorous. It is an attribute of a reflection type variable that gives it the ability to modify the underlying stored data. “Writability” is ultimately determined by the fact that the reflected object stores the original value. Let’s take a look at this example:

var x float64 = 3.4
v := reflect.ValueOf(x)
Copy the code

We pass a copy of x to reflect.valueof, so the interface value created as an argument to reflect.valueof is a copy of X, not x itself.

V. etFloat (7.1)Copy the code

If the above operation succeeds, it does not update x, even though it looks like the variable v was created from x. Instead, it updates a copy of x that exists inside reflection object V, and the variable x itself is not affected at all. It’s confusing and meaningless, so it’s illegal. Writability is designed to avoid this problem.

This may seem weird, but it’s not, and similar situations are common. Consider this line of code:

f(x)
Copy the code

In the above code, we pass a copy of the variable x to the function, so we don’t expect it to change the value of x. If function F is expected to modify variable x, we must pass the address of x (a pointer to x) to function F, as follows:

f(&x)
Copy the code

Same code as above. If you want to modify variable X by reflection, you need to pass a pointer to the modified variable to the reflection library.

First, initialize variable x as usual, and then create a reflection object to it named P:

var x float64 = 3.4
p := reflect.ValueOf(&x) // Note: take the address of x.
fmt.Println("type of p:", p.Type())
fmt.Println("settability of p:", p.CanSet())
Copy the code

Output from the above code:

type of p: *float64
settability of p: false
Copy the code

The reflection object P is not writable, but we don’t want to change p either, in fact we want to change *p. To get the data that P points to, you can call an Elem method of type Value. The Elem method can “dereference” a pointer and then store the result in a reflection Value object v:

v := p.Elem()
fmt.Println("settability of v:", v.CanSet())
Copy the code

The variable v is now a writable reflection object, as verified by the code output above:

settability of v: true
Copy the code

Since v stands for x, we can change the value of x using v.setfloat:

v.SetFloat(7.1)
fmt.Println(v.Interface())
fmt.Println(x)
Copy the code

The above code will print:

7.1
7.1
Copy the code

Just keep in mind that whenever reflection objects modify what they represent, they must get the address of the object they represent

The structure of the body

In the previous example, the variable v itself is not a pointer; it simply derives from a pointer. A common way to apply reflection to a structure is to use reflection to modify certain fields of a structure. As long as we have the address of the structure, we can modify its fields.

Let’s examine the struct type variable t with a simple example.

First, we create a reflection type object that contains a pointer to a structure because it will be modified later. We then set typeOf to its type and iterate through all the fields.

type T struct {
    A int
    B string
}
t := T{23."skidoo"}
s := reflect.ValueOf(&t).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
    f := s.Field(i)
    fmt.Printf("%d: %s %s = %v\n", i,
        typeOfT.Field(i).Name, f.Type(), f.Interface())
}
Copy the code

The above code will print:

0: A int = 23
1: B string = skidoo
Copy the code

It is important to point out that the fields of the variable T are capitalized (exposed) because only exposed fields in structs are “writable”.

Since the variable S contains a “writable” reflection object, we can modify the structure’s fields:

s.Field(0).SetInt(77)
s.Field(1).SetString("Sunset Strip")
fmt.Println("t is now", t)
Copy the code

Output from the above code:

t is now {77 Sunset Strip}
Copy the code

If the variable s is created by t instead of &t, calls to SetInt and SetString will fail because the field of t is not “writable”.

Conclusion:

Golang’s Three laws of reflection:

  1. Reflection can convert “interface type variable” to “Reflection type object”
  2. Reflection can convert “reflection type object” to “interface type variable”
  3. If you want to modify a Reflection type object, its value must be writable

Article Source:

  • Golang’s Three laws of reflection