The three laws of Go language reflection

Source: Internet
Author: User
This is a creation in Article, where the information may have evolved or changed.

Brief introduction

Reflection (reflection) represents the ability of a program to examine its structure, especially the type, in a computer. It is a form of meta-programming and one of the most confusing parts.

In this article, we will explain the workings of reflection in the go language. The reflection model for each programming language is not the same, and many languages simply do not support reflection (C, C + +). Since this article is about the go language, when we talk about "reflection", it is assumed to be reflection in the go language.

Read suggestions

In this article, we will explain the workings of reflection in the go language. The reflection model for each programming language is not the same, and many languages simply do not support reflection (C, C + +).

Since this article is about the go language, when we talk about "reflection", it is assumed to be reflection in the go language.

Although the go language has no concept of inheritance, for ease of understanding, if a struct a implements all the methods of interface B, we call it "inheritance".

Types and Interfaces

Reflection is built on the type system, so we start with type basics.

Go is a statically typed language. Each variable has and has only one static type, which is determined at compile time. such as int, float32, *mytype, []byte. If we make the following statement:

type MyInt intvar i intvar j MyInt

In the above code, the type of the variable i is int,j type is MyInt. So, although the variables I and J have common underlying type int, they are not the same static type. The compiler will give an error if the type conversion is directly assigned to each other.

For types, an important classification is the interface type (interface), each of which represents a fixed collection of methods. An interface variable can store (or "point to", an interface variable like a pointer) any type of concrete value, as long as that value implements all methods of that interface type. A set of well-known examples is IO. Reader and IO. Writer, Reader, and writer types originate from IO packages and are declared as follows:

// 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)}

Any type that implements the Read method is referred to as inheriting IO. Reader (Io.writer) interface. In other words, one type is IO. Reader variables can point to variables of any type (interface variables like pointers), as long as this type implements the Read method:

var r io.Readerr = os.Stdinr = bufio.NewReader(r)r = new(bytes.Buffer)// and so on

Always keep in mind that the type of the variable R is always IO, regardless of the specific value it points to. Reader. Repeat: The go language is a static type language, and the static type of the variable R is IO. Reader.

A very, very important interface type is an empty interface, namely:

interface{}

It represents an empty set, without any method. Because any specific value has 0 or more methods, a variable of type interface{} can store any value.

Some say that the interface of Go is dynamic type. This statement is wrong! An interface variable is also a static type, and it always has only one static type. If the value it stores at run time changes, this value must also satisfy the method collection of the interface type.

Since the relationship between reflection and interface is very close, we must clarify this point.

Representation of interface variables

In 2009, Russ Cox wrote an article about the representation of interface variables in go, referring to the link at the end of the article "Go Language Interface representation". Here we do not need to repeat all the details, just a simple summary.

The interface variable stores a pair of values: the specific value assigned to the variable, and the descriptor for the value type. More precisely, the value is the underlying data that implements the interface, and the type is the description of the underlying data type. As an example:

var r io.Readertty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)if err != nil {    return nil, err}r = tty

In this example, the variable R contains a (value, type) pair on the structure: (TTY, OS. File). Note: type OS. File not only implements the Read method. Although the interface variable only provides the call to the read function, the underlying value contains all the type information about the value. So we can do this type of conversion:

var w io.Writerw = r.(io.Writer)

The second line of the above code is a type assertion, which determines that the actual value inside the variable R also inherits Io. Writer interface, so it can be assigned to W. After the assignment, W points to the (TTY, *os. File) pairs, and the variable r points to the same (value, type) pair. No matter how large the method set of the underlying value is, the interface variable can only invoke certain methods due to the static type restriction of the interface.

We continue to look down:

var empty interface{}empty = w

Here the empty interface variable empty also contains (TTY, *os. File) pair. This is easy to understand: An empty interface variable can store any specific value and all the descriptive information for that value.

A careful friend might find that there is no type assertion here, because the variable w satisfies all the methods of the Null interface (the legendary "No recruit wins the trick"). In the previous example, we put a specific value from IO. Reader converted to IO. Writer requires an explicit type assertion because of IO. Writer's method collection is not IO. A subset of Reader.

It is also important to note that the type in the (value, type) pair must be of a specific type (struct or base type) and cannot be an interface type. Interface types cannot store interface variables.

About the interface, we introduce here, let's look at the three laws of the go language reflection.

Reflection the First Law: reflection you can convert an interface type variable to a reflection type object.

Note: Here the reflection type refers to reflect.Type and reflect.Value .

In terms of usage, reflection provides a mechanism that allows a program to check the interface variables stored internally (value, type) pairs at run time. At the very beginning, let's look at the two types of reflect packages: type and Value. Both of these types make it possible to access data within the interface. They correspond to two simple methods, namely reflect. TypeOf and reflect. ValueOf, respectively, to read the reflect of the interface variable. Type and reflect. Value section. Of course, from reflect. Value is also readily available to reflect. Type. Now let's split them up first.

First, let's look at the reflect. TypeOf:

package mainimport (    "fmt"    "reflect")func main() {    var x float64 = 3.4    fmt.Println("type:", reflect.TypeOf(x))}

This code will print out:

type: float64

You may wonder: Why didn't you see the interface? This code seems to pass a variable x of type float64 to reflect. TypeOf, and there is no delivery interface. In fact, the interface is right there. Check out the typeof documentation and you'll find reflect. The TypeOf function signature contains an empty interface:

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

We call reflect. TypeOf (x), x is stored in an empty interface variable passed past; then reflect. TypeOf the Null interface variable to recover its type information.

function reflect. ValueOf also restores the underlying values (here we ignore the details and focus only on the executable code):

var x float64 = 3.4fmt.Println("value:", reflect.ValueOf(x))

The above code prints out:

value: <float64 Value>

Type reflect. Type and reflect. There are many ways to Value, and we can check and use them. Here we give a few examples. Type reflect. Value has a method Type (), which returns a reflect. The type of the object. Both type and value have a method named Kind, which returns a constant that represents the type of underlying data, with common values such as Uint, Float64, slice, and so on. The value type also has some methods, such as int, float, to extract the underlying data. The int method is used to extract the int64, and the float method is used to extract the float64, referring to the following code:

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

The above code will print out:

type: float64kind is float64: truevalue: 3.4

There are also ways to modify the data, such as Setint, SetFloat, before we discuss them, we need to understand the "modifiable" (settability), which is described in detail in the third law of reflection.

The Reflection library provides a number of properties that are worth discussing separately. The first is to introduce the getter and setter methods of value. To ensure the simplification of the API, these two methods operate on the one of the largest ranges of a set of types. For example, if you are dealing with any number of signed integers, use Int64. That is, the value type of the Int method returns the Int64 type, and the type of the parameter that the Setint method receives is also the int64 type. When actually used, you may need to translate 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.

The second property is the Kind method of a Reflection type variable (reflection object) that returns the type of the underlying data, not the static type. If a reflection type object contains a user-defined integer number, look at the code:

type MyInt intvar x MyInt = 7v := reflect.ValueOf(x)

In the above code, although the static type of the variable V is Myint, the Int,kind method still returns reflect. Int. In other words, the Kind method does not differentiate between MyInt and int as the Type method does.

Reflection Second Law: reflection you can convert a reflection type object to an interface type variable.

Similar to reflection in physics, reflection in the go language can also create objects of its own inverse type.

According to a reflect. A variable of type value, we can use the Interface method to recover the value of its interface type. In fact, this method packages and populates the type and value information into an interface variable and then returns. Its function is declared as follows:

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

Then, we can restore the underlying value by asserting that:

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

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

In fact, we can make better use of this feature. FMT in the standard library. Println and FMT. Printf and other functions all receive NULL interface variables as parameters, the FMT package will be internal to the interface variable unpacking (in the previous example, we have done similar operations). Therefore, the print function of the FMT package is printed reflect. The data of the Value type variable, simply pass the result of the Interface method to the format print program:

fmt.Println(v.Interface())

You may ask: ask what does not directly print V, such as FMT. Println (v)? The answer is that the type of V is reflect. Value, 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.1e\n", v.Interface())

The printed result of the above code is:

3.4e+00

Similarly, this time there is no need to type assert the results of v.interface (). The null interface value internally contains the type information for the specific value, and the PRINTF function recovers the type information.

Simply put, the Interface method works exactly the opposite of the VALUEOF function, except that the static type of the return value is interface{}.

Let's rephrase it: The reflection mechanism of Go can convert "variable of interface type" to "Object of reflection Type" and then convert "Reflection type Object" to past.

Reflection Third law: If you want to modify the reflection type object, its value must be writable (settable).

The law is subtle and confusing. But if you start with the first rule, it should be easier to understand.

The following code does not work, but it is worth studying:

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

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

panic: reflect.Value.SetFloat using unaddressable value

The problem here is not that the value 7.1 cannot be addressed, but that the variable v is "not writable". "Writable" is a property of a reflection type variable, but not all of the reflection type variables have this property.

We can check a reflect by Canset method. The "writable" Value type variable. For the above example, you can write this:

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

The above code prints the result:

settability of v: false

For a value type variable that does not have "writable", calling the Set method will report an error. First, we need to figure out what "writable" is.

"Writable" is somewhat similar to addressing capabilities, but more stringent. It is a property of a reflection type variable that gives the variable the ability to modify the underlying stored data. "Writable" is ultimately determined by a fact: whether the reflected object stores the original value. To cite a code example:

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

Here we pass to reflect. The ValueOf function is a copy of the variable x, not the x itself. Imagine if the following line of code can execute successfully:

v.SetFloat(7.1)

The answer is: if this line of code executes successfully, it does not update X, although it appears that the variable V was created from X. Instead, it updates a copy of x that exists inside the reflected object V, and the variable x itself is completely unaffected. This can be confusing and meaningless, so it's not legal. "Writable" is designed to avoid this problem.

This looks strange, in fact it is not, and similar situations are common. Consider the following line of code:

f(x)

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 the expected function f is able to modify the variable x, we must pass the address of the X (that is, the pointer to x) to the function F, as follows:

f(&x)

You should be familiar with this line of code, and the mechanism of reflection works the same. If you want to modify the variable x through reflection, bite the pointer of the variable you want to modify to pass to the reflection library.

First, initialize the variable x as usual, and then create a reflection object that points to it, with the name P:

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

The output of this code is:

type of p: *float64settability of p: false

The Reflection object P is not writable, but we do not like to modify p, in fact we want to modify the *p. To get the data that P points to, you can call the Elem method of the Value type. The Elem method can "dereference" a pointer and then store the result in the Reflection value type Object V:

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

In the above code, the variable v is a writable reflection object, and the code output verifies this:

settability of v: true

Since the variable v represents x, we can use V.setfloat to modify the value of x:

v.SetFloat(7.1)fmt.Println(v.Interface())fmt.Println(x)

The output of the above code is as follows:

7.17.1

Reflection is not easy to understand, reflect. Type and reflect. Value confuses the executing program, but it does exactly what the programming language does. All you have to remember is that as long as the reflective objects are modifying the objects they represent, they must get the address of the object they represent.

struct (struct)

In the previous example, the variable v itself is not a pointer, it just derives from the pointer. When applying reflection to a struct, it is common to use reflection to modify some fields of a struct. As long as you have the address of the struct, we can modify its fields.

The structure type variable T is analyzed in a simple example below.

First, we create a reflection type object that contains a pointer to a struct, because subsequent modifications are made.

We then set Typeoft to its type and iterate through all the fields.

Note: We extract the name of each field from the struct type, but each field itself is a regular reflect. The Value object.

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())}

The output of the above code is as follows:

0: A int = 231: B string = skidoo

It is also important to note that the fields of the variable T are capitalized (exposed externally) because only fields exposed to the outside of the struct are "writable".

Since the variable s contains a "writable" reflection object, we can modify the fields of the struct body:

f.Interface())s.Field(0).SetInt(77)s.Field(1).SetString("Sunset Strip")fmt.Println("t is now", t)

The output of the above code is as follows:

t is now {77 Sunset Strip}

If the variable s is created through T, instead of &t, calling Setint and SetString will fail because the field of T is not "writable".

Conclusion

Finally repeat the three laws of reflection again:

    1. Reflection can convert an interface type variable to a reflection type object.

    2. Reflection can convert a reflection type object to an interface type variable.

    3. If you want to modify the reflection type object, its value must be writable (settable).
      Once you understand these laws, using reflection will be a very simple thing. It is a powerful tool that must be used with caution, not to be abused.

With regard to reflection, we have a lot to discuss, including pipeline-based send and receive, memory allocation, using slice and map, calling methods and functions, as this article has been very long, and these topics are covered in subsequent articles.

Original author Rob Pike, translator Oscar

RELATED links:

    • Original link: https://blog.golang.org/laws-...

    • Reflect Bag: https://golang.org/pkg/reflect/

Sweep code attention to the public number "go language deep"

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.