Go language Reflex rule-the laws of Reflection

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

Go language Reflex rule-the laws of Reflection

Original address: Http://blog.golang.org/laws-of-reflection

Introduced

Reflection in a computer concept refers to the ability of a program to review its own structure, mainly through the type of review. It is a form of meta-programming and is also a major source of confusion.

In this article we try to illustrate how reflection works in the go language. The reflection model for each language is different (many languages do not support reflection), but this article is only relevant to go, so the next "reflection" we refer to refers to the reflection in the go language.

Type and interface

Since reflection is built on the type system, let's review the types in the Go language first.

Go is a static type of language. Each variable has a static type, and the type is known and determined when it is compiled.

type MyInt intvar i intvar j MyInt

The type of the variable is the type of the i int variable j MyInt . Although they have the same basic types, static types are different, and they cannot be assigned to each other without type conversions.

An interface is an important type, which means a set of methods that are deterministic. An interface variable can store any specific value of the method that implements the interface (except the interface itself). A well-known example is the io.Reader io.Writer following:

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

If a type declaration implements the Reader (or Writer ) method, then it implements io.Reader (or io.Writer ). This means that a io.Reader variable can hold any value that implements the Read type of the method.

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

One thing that must be made clear is that regardless of the r specific value in the variable, r the type is always io.Reader : Go is a static type, and the static type of R is io.Reader .

There is an extremely important example in the interface type--NULL interface:

interface{}

It represents an empty set of methods that all values can satisfy because they all have 0 values or methods.

Some people say that the go interface is a dynamic type, which is wrong. They are all static types: Although the value stored in the interface variable may change during runtime, the type of the interface variable will never change. We must understand these precisely, because reflection is closely related to the interface.

Deep interface

Russ Cox wrote a detailed article about the meaning of interface variables in go in a blog post. We don't need to list the full text, just give a little summary here.

There are two things in a variable of an interface type: The concrete value of the variable and the type description of the value. To be more precise, this implements the value of the interface as a basis for a specific data item, and the type describes all the types in the data item.

As shown below:

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

After this, r a (value, type) combination is included (tty, *os.File) . It is worth noting that the method is not *os.File implemented, although the Read interface value only provides a Read method, but it contains all the type information, which is why we can do it:

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

The expression shown above is a type assertion, which asserts that the r data item contained in it is implemented io.Writer , so we can use it to assign a w value. After this, the w r same as, contains the (tty, *os.File) combination. The static type of the interface determines which methods of the interface variable are called, even if the specific value it contains has a larger set of methods.

Next, we can do this:

var empty interface{}empty = w

Our empty interface variable will contain the same "combination" here: (tty, *os.File) . This is convenient: an empty interface can contain any value and its type information, and we can understand it whenever we need to.

(Here we don't need a type assertion because w an empty interface has been satisfied.) In the previous example we have passed a value from one to the other Reader Writer , because it Writer is not a Reader subset, so we need to use the type assertion. )

Here is an important detail: The format of the "combo" in the interface is always (value, entity type), not (value, interface type). Interface does not contain interface values.

Okay, now let's get into the reflection section.

Reflection rule (i)-from interface to reflection object

On the basis of this, reflection is a mechanism that examines the (type, value) composition of the interface variables. Now, we need to understand the two types in the reflect package: Type and Value , let us access the contents of the interface variables. reflect.TypeOffunctions and reflect.ValueOf functions are returned reflect.Type and reflect.Value can be pieced together to create an interface value. (Of course, it's easy to reflect.Value get reflect.Type , but now let's take Value Type a separate view of the concept.) )

We TypeOf start from:

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

This program prints out:

type: float64

Looking at this code, you might think, "Where is the interface?" ", there are only variables in this program float64 x , and no interface variables are passed in reflect.TypeOf . In fact, it is here: in the declaration of Godoc reports, reflect.TypeOf an empty interface is included:

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

When we call reflect.TypeOf(x) , the passed in as a parameter x has been stored in an empty interface. The reflect.TypeOf empty interface is unpacked and the type information it contains is restored.

In contrast, the reflect.ValueOf function restores the value (from here we will modify the example and focus only on the executable code):

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

Print:

Value: <float64 value>

reflect.TypeAnd reflect.Value There are many ways that we can review and manipulate interface variables. An important example is Value Type the return of a method reflect.Value Type . Another example is that, Type and Value both have Kind methods that return a constant that represents the order in which the stored elements are arranged: and Uint, Float64, Slice so on. And, Value a series of methods (such as Int or Float ), allows us to get the stored values (such as int64 or float64 ):

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

Print:

type: float64kind is float64: truevalue: 3.4

There are some methods, such as the SetInt SetFloat settability concept, which is the third article of the reflection rule, which we will discuss later.

The reflection library has two characteristics that need to be pointed out. One, in order to maintain the simplicity of the API, Value the getter and setter method is to use the largest type to manipulate the data: for example, let all the integral type use the int64 representation. So, Value the Int method returns a int64 value that SetInt needs to pass int64 in the parameter; Converting a value to its actual type is necessary at some point:

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

Second, the method that reflects the object Kind describes the underlying type, not the static type. If a reflection object contains a value for a user-defined type, the following:

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

Although x the static type is not MyInt int , but v Kind still is reflect.Int . Although Type it can be distinguished int from the open and MyInt , but Kind unable to do.

Reflection Rule (ii)-from reflected object to interface

Like reflection in physics, the reflection of the go language is reversible.

By a reflect.Value method we can use to Interface recover an interface; This method packages the type and value information into an interface and returns it:

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

So we get a result:

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

The above code prints v the values shown by the reflected object float64 .

However, we can still do better. fmt.Printlnand fmt.Printf The arguments are passed through interface{}, and the fmt private method is unpacked after passing in (as we did in the previous example). It is because the fmt Interface return result of the method is passed to the formatted print transaction (formatted print routine), so the program can print reflect.Value the content correctly:

fmt.Println(v.Interface())

(Why not fmt.Println(v) ?) Because V is one reflect.Value , and what we want is its specific value)
Because the type of value is float64 , we can print it with floating-point formatting:

fmt.Printf("value is %7.1e\n", v.Interface())

And come to the result:

3.4e+00

Here we do not need to v.Interface() do type assertion, this null interface value contains the specific value of the type information, Printf will restore it.

In short, the Interface method is ValueOf the inverse of the function, unless ValueOf the type of the result isinterface{}

Again: Reflection from the interface, through the reflection of the object, and back to the interface.
(Reflection goes from interface values to Reflection objects and back again.)

Reflection Rule (iii)-To modify a reflection object, the value must be set

The third rule is the most subtle and the most chaotic, but if we start with its fundamentals, then everything is a cinch.

The following code, while not working, is worth learning:

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

If you run this code, it will panic these cryptic messages:

panic: reflect.Value.SetFloat using unaddressable value

The problem 7.1 is that it is not addressable, which means it v becomes unavailable. "Configurable" (settability) is reflect.Value one of the features, but not all of Value them can be set.

ValueCanSetmethod returns a Boolean value that indicates whether this Value can be set:

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

Print:

settability of v: false

The method for a non-set Value call Set is wrong. So, what is "can be set"?

"Configurable" and "addressable" (addressable) are somewhat similar but stricter. A reflection object can modify the actual content that created it, which is "can be set". The "orginal" of the reflected object is determined by whether it owns the original project (item).

When we do this:

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

We pass a x copy to the reflect.ValueOf , so the interface value that is passed to is reflect.ValueOf not x , but is x created by the copy. Therefore, if the following statements

v.SetFloat(7.1)

is allowed to execute successfully, it will not be updated x even if it appears to v be x created by. Instead, it updates the copy that is stored in the reflected value x and is x not affected by itself. It's confusing and useless, so it's illegal to do so. One of the characteristics of "can be set" as a reflection is to prevent such a situation.

It looks weird, but the opposite is true. It is actually a situation that we are very familiar with, and it just wears an unusual coat. Think about x how it's passed into a function:

f(x)

We don't expect f to be able to modify it x because we're passing a x copy, not a x . If we want to make a f direct modification x We must give our function an incoming x address (that is, a x pointer):

f(&x)

This is straightforward and familiar, and the reflection works the same way. If we want to modify with reflection x , we must pass the pointer of the value to the Reflection library.

Let's go. First we initialize it just as we did x , and then we create a reflection value to it, named 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 current output is:

type of p: *float64settability of p: false

The Reflection object p is not configurable, but what we want to set is not it, but *p .
To know p where the point is, we call Value the Elem method, which is directed by the pointer and saves the result in one Value , named v :

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

Now v is a set of reflective objects, as follows:

settability of v: true

Because it says x that we can finally use v.SetFloat to modify x the value:

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

As was expected:

7.17.1

Reflection can be difficult to understand, but what it does is what the programming language does, although the reflection type and value can disguise what is happening.
Remember, when you modify data with reflection, you need to pass in a pointer to it.

Structural body

In the previous example, v it is not the pointer itself, it just comes from this.
We typically use reflection to modify the structure field. As long as we have a pointer to the struct, we can modify its field.

Here is an example of parsing struct variables t . We created the reflection variable with the address of the struct, and we'll change it later. Then we set the type of it typeOfT and iterate the field with a simple method called (see reflect package for details).
Note that we extracted the names of the fields from the struct type, but each field itself is a normal reflect.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())}

Program output:

0: A int = 231: B string = skidoo

There is one more thing to note about the setting: T the field name is uppercase (field exportable/public field) because only the exportable fields in the struct are "configurable".

Because it s contains a set of reflective objects, we can modify the struct fields:

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

Results:

t is now {77 Sunset Strip}

If we modify the program so that it is s t created by (not &t ), the program will SetInt fail where it was called SetString , because t the fields are not set.

Conclusion

The reflection rule is listed again:
* Reflection from the interface value into the Reflection object (Reflection goes from interface value to Reflection object.)
* Reflection from the reflected object into the interface value (Reflection goes from Reflection object to interface value.)
* To modify the reflected object, the value must be "configurable" (to modify a reflection object, the value must is settable.)

Once you understand the law of reflection, go becomes more handy (though it is still subtle). This is a powerful tool, except when absolutely necessary, we should be cautious and avoid using it.

We also have a lot of reflection knowledge without mentioning--chan's send and receive, memory allocations, using slice and maps, calling methods and functions--but this article is long enough. We will cover these in a future article.

Address: https://github.com/cuebyte/The-Laws-of-Reflection/blob/master/README.md
by Rob Pike

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.