An article to understand what is CanSet, CanAddr?

What is settable (CanSet)

First of all, you need to make it clear that the settings are for reflect.Value. To convert ordinary variables into reflect.Value, you need to use reflect.ValueOf() to convert.

So why is there such a "settable" method? For example, the following example:

var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println(v.CanSet()) // false

All function calls in golang are value replication, so when reflect.ValueOf is called, an x ​​has been copied and passed in. The v obtained here is the value of a copy of x. So at this time, we want to know if I can set the x variable here through v. We need a method to help us do this: CanSet()

However, it is very obvious that since we are passing a copy of x, the value of x cannot be changed at all. It shows false here.

So what if we pass the address of x to it? The following example:

var x float64 = 3.4
v := reflect.ValueOf(&x)
fmt.Println(v.CanSet()) // false

We passed the address of the x variable to reflect.ValueOf. It should be CanSet. But here is one thing to pay attention to, here v points to the pointer of x. So the CanSet method judges whether the pointer of x can be set. The pointer cannot be set, so false is returned here.

So what we need to be able to judge by the value of this pointer is whether the element pointed to by this pointer can be set. Fortunately, reflect provides the Elem() method to get the "element pointed to by the pointer".

var x float64 = 3.4
v := reflect.ValueOf(&x)
fmt.Println(v.Elem().CanSet()) // true

Finally returns true. But there is a prerequisite when using this Elem(), the value here must be reflect.Value converted from pointer object. (Or reflect.Value converted by interface object). This premise is not difficult to understand, if it is an int type, how can it have a pointed element? So, pay attention to this when using Elem, because if this premise is not met, Elem will directly trigger panic.

After judging whether it can be set, we can use SetXX series methods to make the corresponding settings.

var x float64 = 3.4
v := reflect.ValueOf(&x)
if v.Elem().CanSet() {
    v.Elem().SetFloat(7.1)
}
fmt.Println(x)

More complex types

For complex slice, map, struct, pointer and other methods, I wrote an example:

package main

import (
	"fmt"
	"reflect"
)

type Foo interface {
	Name() string
}

type FooStruct struct {
	A string
}

func (f FooStruct) Name() string {
	return f.A
}

type FooPointer struct {
	A string
}

func (f *FooPointer) Name() string {
	return f.A
}

func main() {
	{
		// slice
		a := []int{1, 2, 3}
		val := reflect.ValueOf(&a)
		val.Elem().SetLen(2)
		val.Elem().Index(0).SetInt(4)
		fmt.Println(a) // [4,2]
	}
	{
		// map
		a := map[int]string{
			1: "foo1",
			2: "foo2",
		}
		val := reflect.ValueOf(&a)
		key3 := reflect.ValueOf(3)
		val3 := reflect.ValueOf("foo3")
		val.Elem().SetMapIndex(key3, val3)
		fmt.Println(val) // &map[1:foo1 2:foo2 3:foo3]
	}
	{
		// map
		a := map[int]string{
			1: "foo1",
			2: "foo2",
		}
		val := reflect.ValueOf(a)
		key3 := reflect.ValueOf(3)
		val3 := reflect.ValueOf("foo3")
		val.SetMapIndex(key3, val3)
		fmt.Println(val) // &map[1:foo1 2:foo2 3:foo3]
	}
	{
		// struct
		a := FooStruct{}
		val := reflect.ValueOf(&a)
		val.Elem().FieldByName("A").SetString("foo2")
		fmt.Println(a) // {foo2}
	}
	{
		// pointer
		a := &FooPointer{}
		val := reflect.ValueOf(a)
		val.Elem().FieldByName("A").SetString("foo2")
		fmt.Println(a) //&{foo2}
	}
}

If you can understand the above example, then you basically understand the CanSet method.

In particular, you can pay attention to the fact that map and pointer do not need to pass pointers to reflect.ValueOf when they are modified. Because they are pointers themselves.

So when calling reflect.ValueOf, we must be very clear in mind, the underlying structure of the variable we want to pass. For example, map actually passes a pointer, we don't need to pointer it anymore. And slice, what actually passes is a SliceHeader structure. When we modify Slice, we must pass the pointer of SliceHeader. This point often requires our attention.

CanAddr

As you can see in the reflect package, in addition to CanSet, there is also a CanAddr method. What is the difference between the two?

The difference between the CanAddr method and the CanSet method is that for some private fields in the structure, we can get its address, but we cannot set it.

For example, the following example:

package main

import (
	"fmt"
	"reflect"
)

type FooStruct struct {
	A string
	b int
}


func main() {
	{
		// struct
		a := FooStruct{}
		val := reflect.ValueOf(&a)
		fmt.Println(val.Elem().FieldByName("b").CanSet())  // false
		fmt.Println(val.Elem().FieldByName("b").CanAddr()) // true
	}
}


Therefore, CanAddr is a necessary and insufficient condition for CanSet. A Value if CanAddr is not necessarily CanSet. But if a variable is CanSet, it must be CanAddr.

Source code

Suppose we want to implement the Value element CanSet or CanAddr, we will probably use the marker bit mark. This is indeed the case.

Let's first look at the structure of Value:

type Value struct {
	typ *rtype
	ptr unsafe.Pointer
	flag
}

It should be noted here that it is a nested structure with a flag nested, and this flag itself is a uintptr.

type flag uintptr

This flag is very important. It can not only express the type of the value, but also some meta information (such as whether it is addressable, etc.). Although flag is a uint type, it is represented by a bit mark.

First, it needs to represent the type. There are 27 types in golang:

const (
	Invalid Kind = iota
	Bool
	Int
	Int8
	Int16
	Int32
	Int64
	Uint
	Uint8
	Uint16
	Uint32
	Uint64
	Uintptr
	Float32
	Float64
	Complex64
	Complex128
	Array
	Chan
	Func
	Interface
	Map
	Ptr
	Slice
	String
	Struct
	UnsafePointer
)

So using 5 bits (2^5-1=63) is enough to put so many types. So the lower 5 bits of the flag are the structure type.

Sixth flagStickyRO: whether the flag is an internal structure Private property
seventh flagEmbedR0: whether the flag is an internal structure of a private property nested
eighth flagIndir: whether a flag value of ptr is a pointer to a stored
ninth flagAddr: this marker Is the value addressable? The
tenth bit flagMethod: mark that value is an anonymous function

20201026181333

One of the more difficult to understand is flagStickyRO, flagEmbedR0

Look at the following example:


type FooStruct struct {
	A string
	b int
}

type BarStruct struct {
	FooStruct
}

{
    	b := BarStruct{}
        val := reflect.ValueOf(&b)
        c := val.Elem().FieldByName("b")
		fmt.Println(c.CanAddr())
}

In this example, the flagEmbedR0 flag bit of c is 1.

So let’s go back to the CanSet and CanAddr methods

func (v Value) CanAddr() bool {
	return v.flag&flagAddr != 0
}

func (v Value) CanSet() bool {
	return v.flag&(flagAddr|flagRO) == flagAddr
}

Their method is to "AND" the value's flag and flagAddr or flagRO (flagStickyRO, flagEmbedR0).

The difference between them is whether to judge the two bits of flagRO. So their difference, in other words, is to "judge whether this Value is a private property", and the private property is read-only. Cannot be set.

application

In the process of  developing the collection ( https://github.com/jianfengye/collection) library, I used such a method. I hope to design a methodfunc (arr *ObjPointCollection) ToObjs(objs interface{}) error that can set the objs reflect.Value in ObjPointCollection to the parameter objs.

func (arr *ObjPointCollection) ToObjs(objs interface{}) error {
	arr.mustNotBeBaseType()

	objVal := reflect.ValueOf(objs)
	if objVal.Elem().CanSet() {
		objVal.Elem().Set(arr.objs)
		return nil
	}
	return errors.New("element should be can set")
}

Instructions:

func TestObjPointCollection_ToObjs(t *testing.T) {
	a1 := &Foo{A: "a1", B: 1}
	a2 := &Foo{A: "a2", B: 2}
	a3 := &Foo{A: "a3", B: 3}

	bArr := []*Foo{}
	objColl := NewObjPointCollection([]*Foo{a1, a2, a3})
	err := objColl.ToObjs(&bArr)
	if err != nil {
		t.Fatal(err)
	}
	if len(bArr) != 3 {
		t.Fatal("toObjs error len")
	}
	if bArr[1].A != "a2" {
		t.Fatal("toObjs error copy")
	}
}

to sum up

When CanAddr and CanSet first come into contact, there will be some confusion, or you need to understand the flag of reflect.Value to fully understand it.

Guess you like

Origin blog.csdn.net/weixin_51204715/article/details/109308785