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&xA copy of.
  • throughreflect.ValueOf(x)The returnedreflect.ValueObjects are not addressable.
  • D is derived by lifting a pointer to C, so it is addressable.
  • callreflect.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:

  1. An element of a slice
  2. An element of an Addressable array
  3. A field of an addressable struct
  4. Parse the result of dereferencing a pointer

Getting a variable from an addressable reflect.value () takes three steps:

  1. callAddr(), returns aValueContains a pointer to a variable.
  2. In thisValueOn the callInterface(), and get one that contains the pointerinterface{}Value.
  3. 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:

  1. First, callreq.ParseForm()To parse the request. And after that,req.FormI have all the request parameters, this method pairHTTPtheGETandPOSTAll requests apply.
  2. 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.TypetheField()Method returns onereflect.StructFieldType, which provides the name of each field, the type, and an optional label. It’sTagThe 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.
  3. In the end,Unpack()traverseHTTPParameter, and update the corresponding structure field. Note that the same parameter may appear more than once. If this is the case and the field issliceType, all values of this parameter are appended tosliceIn 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