On the Three laws of reflection in go language _golang

Source: Internet
Author: User
Tags inheritance reflection

Brief introduction

Reflection (reflection) indicates in the computer the ability of a program to examine its own structure, especially the type. It is a form of metaprogramming and one of the most confusing parts.

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

Types and Interfaces

Reflection is based on the type system, so we talk about the type basics.

Go is a static type 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 int

var i int
var j MyInt

In the code above, the type of the variable i is the type of int,j is MyInt. Therefore, although the variables I and J have common underlying type int, their static types are not the same. The compiler complains when you assign values directly to each other without a type conversion.

An important classification for types is the interface type (interface), each of which represents a fixed collection of methods. An interface variable can be stored (or "point", an interface variable is similar to a pointer) to any type of specific value, as long as the value implements all the methods of that interface type. A well-known set of examples is the io.Reader and io.Writer , Reader and Writer types from IO packages, declared as follows:

The Reader is the interface which wraps the basic Read method.
Type Reader Interface {
 Read (p []byte) (n int, err error)
}

//Writer is the interface that wraps the basic Wr Ite method.
Type Writer Interface {
 Write (p []byte) (n int, err error)
}

Any Read(Write) type that implements a method is called an inherited io.Reader(io.Writer) interface. In other words, a io.Reader variable of a type can point (an interface variable is similar to a pointer) to any type of variable, as long as that type implements the Read method:

var r io. Reader
r = os. Stdin
r = Bufio. Newreader (r)
r = new (bytes. Buffer)
//And so on

Keep in mind that the type of the variable R is always IO, regardless of the specific value it points to. Reader. Repeat: The Golanguage 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 interface{} variable of type can store any value.

It is said that the go interface is dynamic type. This is a false statement! An interface variable is also statically typed, and it always has only one static type. If the value that it stores at run time changes, this value must also satisfy the collection of methods for the interface type.

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

Representation of interface variables

Russ Cox wrote an article in 2009 about the representation of interface variables in go, where we don't need to repeat all the details, just to make a simple summary.

The interface variable stores a pair of values: the specific value assigned to the variable, the descriptor for the value type. To be more precise, a 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. Reader
TTY, 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 in the structure: (TTY, OS. File). Note: type OS. File does not only implement the Read method. Although an interface variable provides only the invocation of the Read function, the underlying value contains all the type information about the value. So we can do this type conversion:

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

The second line of the code above is a type assertion, which determines that the actual value within the variable R also inherits Io. Writer interface, so you can be assigned a value to W. After assigning a value, W points to (TTY, *os. File) pairs, and the variable r points to the same (value, type) pair. The interface variable can only invoke certain methods, regardless of the size of the method set of the underlying concrete value, due to the static type restrictions of the interface.

We continue to look down:

var empty interface{}
empty = W

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

Careful friends may find that no type assertion is used here, because the variable w satisfies all the methods of the Null interface (the legendary "No strokes wins there are strokes"). In the previous example, when we convert a specific value from io.Reader to io.Writer , we need an explicit type assertion, because io.Writer the method set is not io.Reader a subset.

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

As for the interface, let's take a look at the three laws of reflection in the go language.

Reflection First Law: Reflection 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 (value, type) pairs stored inside an interface variable at run time. At the very beginning, we first look at the two types of reflect packages:type and Value. These two types make it possible to access data within the interface. They correspond to two simple methods, respectively, to reflect.TypeOf reflect.ValueOf Read and part of the interface variables reflect.Type reflect.Value . Of course, it reflect.Value 's easy to get reflect.Type . Now we're going to separate them.

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

Package main

Import (
 "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 not see the interface? This code seems to pass a float64 type of variable x to reflect.TypeOf , and does not pass the interface. In fact, the interface is right there. Check out TypeOf's 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

When we call reflect.TypeOf(x) , x is stored in an empty interface variable that is passed through; and then reflect. TypeOf the Null interface variable to recover its type information.

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

var x float64 = 3.4
fmt. Println ("Value:", reflect.) valueof (x))

The above code prints:

Value: <float64 value>

Types reflect.Type and reflect.Value There are many ways that we can check and use them. Here we give a few examples. A type reflect.Value has a method type () that returns an reflect.Type object of a type. Both type and value have a method named Kind, which returns a constant representing the type of underlying data, common values:Uint, Float64, Slice , and so on. The value type also has a number of methods similar to int and 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.4
V: = 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:float64
Kind is float64:true
value:3.4

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

The Reflection library provides a number of properties that deserve to be listed for individual discussion. First, the getter and setter methods for value are introduced. To ensure that the API is streamlined, the two methods operate on the largest group of types. For example, you can use int64 to handle any number of symbol integers. That is, the value type's int method returns a int64 type, and the Setint method receives a type of parameter that is also a int64 type. When you actually use it, you might want to turn 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 the 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 int
var x MyInt = 7
V: = reflect. valueof (x)

In the code above, although the static type of the variable V is Myint, not 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.

Reflection of the second law: Reflection can Convert "reflection type Object" to "interface type variable".

Like reflection in physics, reflection in Go can also create objects of its own opposite type.

According to a reflect. Variable of type value, we can use the Interface method to recover the value of its interface type. In fact, this method packs the type and value information into an interface variable and returns. The function declaration is as follows:

Interface returns V ' s value as an interface{}.
Func (v Value) Interface () interface{}

We can then restore the underlying values by asserting:

Y: = V.interface (). (float64)//Y would have type float64.
Fmt. Println (y)

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

In fact, we can make better use of this feature. The and equal functions in the standard library fmt.Println fmt.Printf receive NULL interface variables as parameters, and the FMT package internally splits the interface variables (we have done similar operations in the previous example). Therefore, the print function of the FMT package is printed reflect. Value type variable, you simply pass the result of the Interface method to the formatted print program:

Fmt. Println (V.interface ())

You may 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. Because 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

Again, there is no need for v.Interface() a type assertion of the result. The null interface value contains the type information for the specific value, and the Printf function restores the type information.

Simply put, the Interface method and ValueOf function function exactly the opposite, the only point is that the return value of the static type is interface{}。

let us rephrase: The go reflection mechanism converts "variable of interface type" to "Object of reflection Type", and then converts the reflection type object to the past.

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

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

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

var x float64 = 3.4
V: = reflect. valueof (x)
v.setfloat (7.1)//Error:will panic.

If you run this piece of code, it throws out 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 reflection type variables have this property.

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

var x float64 = 3.4
V: = reflect. valueof (x)
FMT. Println ("Settability of V:", V.canset ())

The above code prints the result:

Settability of V:false

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

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

var x float64 = 3.4
V: = reflect. valueof (x)

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

V.setfloat (7.1)

The answer is: If this line of code succeeds, it does not update x, although it looks like the variable V was created from X. Instead, it updates a copy of x that exists inside the Reflection Object V, and the variable x itself is completely unaffected. This can cause confusion and makes no sense, so it is illegal. "Writable" is designed to avoid this problem.

It looks weird, but it's not, and it's a common situation. Consider the following line of code:

F (x)

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

F (&x)

You should be familiar with this line of code, the working mechanism of reflection is the same. If you want to modify the variable x by reflection, bite the pointer to 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.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 ())

The output of this code is:

Type of P: *float64
settability 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" the pointer and then store the result in reflection value type Object V:

V: = P.elem ()
FMT. Println ("Settability of V:", V.canset ())

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

Settability of V:true

Because 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.1
7.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. You just have to remember that as long as the reflection objects are modifying the objects they represent, they must get the address of the object they represent.

Structural body (struct)

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

The following is a simple example to analyze the structure type variable T.

First, we created the reflection type object, which contains a pointer to a struct, because the subsequent modifications are made.

Then we set the 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 =
1:b string = Skidoo

Here's another point to note: The fields of variable T are all capitalized (exposed to the outside), because only fields exposed to the outside are "writable" in struct.

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

F.interface ()) S.field (0). Setint (S.field)
(1). SetString ("Sunset Strip")
FMT. Println ("T is now", T)

The output of the above code is as follows:

T is now {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

And then again, repeat the Three Laws of reflection:

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

2. Reflection can Convert "reflection type Object" to "interface type variable".

3. If you want to modify the reflection type object, the 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 care and not abused.

As for reflection, we still have a lot of content to discuss, including piped delivery and receiving, memory allocation, using slice and map, calling methods and functions, which we'll cover in a later article. Please continue to focus on the cloud-dwelling community.

Original author Rob Pike, translation Oscar

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.