Go basics: Use reflection to modify the value of a variable
Content of this article:
- The addressable concept of Go language reflection
- The modifiable concept of Go language reflection
- Examples of use of Go language reflection
addressable
Recall that expressions in the Go language, such as x, x.f[1], *p represent a variable, while expressions such as x+1, f(2) do not. A variable is an addressable storage area that contains a value and can be updated at that address.
There is a similar distinction for reflect.value, some of which are addressable:
x := 2 // Whether it is addressable
a := reflect.ValueOf(2) // no
b := reflect.ValueOf(x) // no
c := reflect.ValueOf(&x) // no
d := c.Elem() // yes(x)
Copy the code
- The value in a is not addressable; it contains a copy of the integer 2.
- B: Same thing.
- The value in C is also unaddressable, it contains Pointers
&x
A copy of. - through
reflect.ValueOf(x)
The returnedreflect.Value
Objects are not addressable. - D is derived by lifting a pointer to C, so it is addressable.
- call
reflect.ValueOf(&x).Elem()
We can get the addressable Value of any variable x.
The reflect.value variable can be asked if it is addressable via the variable’s CanAddr() method:
fmt.Println(a.CanAddr()) // false
fmt.Println(b.CanAddr()) // false
fmt.Println(c.CanAddr()) // false
fmt.Println(d.CanAddr()) // true
Copy the code
We can get an addressable reflect.Value object indirectly through a pointer, even if the pointer itself is not addressable.
Common rules for addressing are clearly stated in the comments to the CanAddr() method of reflection packages:
- An element of a slice
- An element of an Addressable array
- A field of an addressable struct
- Parse the result of dereferencing a pointer
Getting a variable from an addressable reflect.value () takes three steps:
- call
Addr()
, returns aValue
Contains a pointer to a variable. - In this
Value
On the callInterface()
, and get one that contains the pointerinterface{}
Value. - Finally, if we know the type of the variable, use type assertions to convert the interface content into a plain pointer.
The variable can then be updated using this pointer.
Set()
In addition to updating variables via Pointers, you can also update variables directly by calling the reflect.value.set () method.
d.Set(reflect.ValueOf(4))
fmt.Println(x) / / "4"
Copy the code
The assignment condition is typically checked by the compiler at compile time, in this case by the Set() method at run time. The above variables and values are int, but if the variable type is INT64, the program will crash.
// crash: int64 cannot be assigned to int
d.Set(reflect.ValueOf(int64(5)))
Copy the code
Of course, calling Set() on an unaddressable reflect.value also crashes:
// crash: use Set() on non-addressable values
b.Set(reflect.ValueOf(3))
Copy the code
In addition to the basic Set() method, there are some derived functions: SetInt(), SetUint(), SetString(), SetFloat(), and so on
These methods also have a degree of fault tolerance. This works as long as the variable type is some kind of signed integer, such as SetInt(), or even a named type whose underlying type is a signed integer. If the value is too large it will be truncated without prompting. Note, however, that calling SetInt() on reflect.value to the interface{} variable crashes, while calling Set() is fine.
x := 1
rx := reflect.ValueOf(&x).Elem()
rx.SetInt(2) // OK, x = 2
rx.Set(reflect.ValueOf(3)) // OK, x = 3
rx.SetString("hello") // crash: strings cannot be assigned to integers
rx.Set(reflect.ValueOf("hello")) // crash: strings cannot be assigned to integers
var y interface{}
ry := reflect.ValueOf(&y).Elem()
ry.SetInt(2) // crash: call SetInt on Value pointing to the interface
ry.Set(reflect.ValueOf(3)) // OK, y = int(3)
ry.SetString("hello") // crash: call SetString on the Value pointing to the interface
ry.Set(reflect.ValueOf("hello")) // OK, y = "hello"
Copy the code
Can be modified
Reflection can read the values of unexported structure fields, but cannot update them.
An addressable reflect.Value records whether it was obtained by iterating through an unexported field, and is not allowed to be modified if it was obtained by an unexported field. So checking with CanAddr() before updating a variable is no guarantee of correctness. The CanSet() method correctly reports whether a reflect.value is addressable and changeable.
Use the sample
- Access the structure field tag
- Methods to display types
Access the structure field tag
After learning the basic uses of reflection, let’s write a sample program.
In a Web server, the first thing most HTTP processing functions do is extract the request parameters into local variables. We’ll define a utility function, Unpack(), that uses struct field tags to simplify writing HTTP handlers.
First, we show how to use this method. The search() function below is an HTTP handler that defines a variable data whose type is an anonymous structure corresponding to the HTTP request parameters. The structure’s field labels specify parameter names, which are usually short and vague because urls are too short to waste. The Unpack function extracts data from the request to populate the structure, which not only makes it easy to access, but also avoids manual conversions.
func search(resp http.ResponseWriter, req *http.Request) {
var data struct{
Labels []string `http:"1"`
MaxResults int `http:"max"`
Exact bool `http:"x"`
}
data.MaxResults = 10
iferr := Unpack(req, &data); err ! =nil {
http.Error(resp, err.Error(), http.StatusBadRequest) / / 400
return}}Copy the code
Let’s focus on the implementation of the Unpack() function:
func Unpack(req *http.Request, ptr interface{}) error {
iferr := req.ParseForm(); err ! =nil {
return err
}
// Create a field mapping table with keys as valid names and values as field names
fields := make(map[string]reflect.Value)
v := reflect.ValueOf(ptr).Elem()
for i := 0; i < v.NumField(); i++ {
fieldInfo := v.Type().Field(i)
tag := fieldInfo.Tag
name := tag.Get("http")
if name == "" {
name = strings.ToLower(fieldInfo.Name)
}
fields[name] = v.Field(i)
}
// Update the corresponding field in the structure for each parameter in the request
for name, values := range req.Form {
f := fields[name]
if! f.IsValid() {continue // Ignore unrecognized HTTP parameters
}
for _, value := range values {
if f.Kind() == reflect.Slice {
elem := reflect.New(f.Type().Elem()).Elem()
iferr := populate(elem, value); err ! =nil {
return fmt.Errorf("%s : %v", name, err)
}
f.Set(reflect.Append(f, elem))
} else {
iferr := populate(f, value); err ! =nil {
return fmt.Errorf("%s : %v", name, err)
}
}
}
}
return nil
}
Copy the code
The following Unpack() function does three things:
- First, call
req.ParseForm()
To parse the request. And after that,req.Form
I have all the request parameters, this method pairHTTP
theGET
andPOST
All requests apply. - And then,
Unpack()
The function constructs one from eachValid field nameMapping to corresponding field variables. When a field has a label, the valid field name may differ from the actual field name.reflect.Type
theField()
Method returns onereflect.StructField
Type, which provides the name of each field, the type, and an optional label. It’sTag
The field type isreflect.StructTag
, the underlying type isstring, provides oneGet()
The parse and extract method is used to parse and extract substrings for a particular key. - In the end,
Unpack()
traverseHTTP
Parameter, and update the corresponding structure field. Note that the same parameter may appear more than once. If this is the case and the field isslice
Type, all values of this parameter are appended toslice
In the water. If not, the field is overwritten multiple times, and only the last value is valid.
The populate() function is responsible for populating a single field V (or each element in a slice field) from a single HTTP request parameter value. Right now, it supports only strings, signed integers, and Booleans. For other types of support, you can practice on your own.
func populate(v reflect.Value, value string) error {
switch v.Kind() {
case reflect.String:
v.SetString(value)
case reflect.Int:
i, err := strconv.ParseInt(value, 10.64)
iferr ! =nil {
return err
}
v.SetInt(i)
case reflect.Bool:
b, err := strconv.ParseBool(value)
iferr ! =nil {
return err
}
v.SetBool(b)
default:
return fmt.Errorf("unsupported kind %s", v.Type())
}
return nil
}
Copy the code
Methods to display types
Use reflect.Type to display the Type of an arbitrary value and enumerate its methods:
func Print(x interface{}) {
v := reflect.ValueOf(x)
t := v.Type()
fmt.Printf("type %s\n", t)
for i := 0; i < v.NumMethod(); i++ {
methodType := v.Method(i).Type()
fmt.Printf("func (%s) %s%s\n", t, t.Method(i).Name, strings.TrimPrefix(methodType.String(), "func"))}}Copy the code
Both reflect.type and reflect.value have a Method called Method(), and each t.thod (I) returns an instance of reflect.method that describes the name and Type of the Method. And each v.method () returns an instance of reflect.method type bound to the receiver Method.
A Value of type Func can be called using the reflect.value.call () method, but this example program only needs its type.
Here is a list of methods of the two types time.Duration and *strings.Replacer:
Print(time.Hour)
// type time.Duration
// func (time.Duration) Hours() float64
// func (time.Duration) Microseconds() int64
// func (time.Duration) Milliseconds() int64
// func (time.Duration) Minutes() float64
// func (time.Duration) Nanoseconds() int64
// func (time.Duration) Round(time.Duration) time.Duration
// func (time.Duration) Seconds() float64
// func (time.Duration) String() string
// func (time.Duration) Truncate(time.Duration) time.Duration
Print(new(strings.Replacer))
// type *strings.Replacer
// func (*strings.Replacer) Replace(string) string
// func (*strings.Replacer) WriteString(io.Writer, string) (int, error)
Copy the code