看别人写的反射代码,真的是看一遍忘一遍,难受...但是吧,该学还的学。
Go语言的反射,这里从一个问题说起吧。
偶然在代码中看到这样一段代码,如下:
// Body body decoder
func Body(r io.Reader, obj interface{}) error {
if w, ok := obj.(io.Writer); ok {
_, err := io.Copy(w, r)
return err
}
all, err := ioutil.ReadAll(r)
if err != nil {
return err
}
// LoopElem 不停地对指针解引用
value := core.LoopElem(reflect.ValueOf(obj))
if value.Kind() == reflect.String {
value.SetString(core.BytesToString(all))
return nil
}
if _, ok := value.Interface().([]byte); ok {
value.SetBytes(all)
return nil
}
fn, ok := convertBodyFunc[value.Kind()]
if ok {
return fn.cb(core.BytesToString(all), fn.bitSize, emptyField, value)
}
return fmt.Errorf("type (%T) %s", value, core.ErrUnknownType)
}
其实这是一个开源的go http client [github.com/guonaihong/gout]。
在代码21行有一个[]byte判断,前面还有一个解索引。
第一反应,obj传入一个[]byte类型应该没问题,因为断言判断的就是[]byte类型,core.LoopElem只是不断的解引用,好像没有影响。core.LoopElem如下:
// LoopElem 不停地对指针解引用
func LoopElem(v reflect.Value) reflect.Value {
for v.Kind() == reflect.Ptr {
if v.IsNil() {
return v
}
v = v.Elem()
}
return v
}
但是测试时发现obj传入[]byte类型时却报错了,“using unaddressable value”。通过日志查看了一下调用栈,value.SetBytes(all)报错了。
错误信息很明显,“不可寻址的值”,因为本来就不是指针当然不能寻址了。但是明明前面的代码里写了这句,这不明显在判断类型为[]byte嘛,为啥又不行,自行车不要了?
if _, ok := value.Interface().([]byte); ok {
value.SetBytes(all)
return nil
}
最后跟踪下来发现在SetBytes里有个函数mustBeAssignableSlow进行了指针判断
在下面代码第9行有过一个if f&flagAddr == 0判断
func (f flag) mustBeAssignableSlow() {
if f == 0 {
panic(&ValueError{methodName(), Invalid})
}
// Assignable if addressable and not read-only.
if f&flagRO != 0 {
panic("reflect: " + methodName() + " using value obtained using unexported field")
}
if f&flagAddr == 0 {
panic("reflect: " + methodName() + " using unaddressable value")
}
}
那v.Elem()到底在取什么,不是解索引吗,以C语言的解索引来看,解索引就是取地址指向的值,返回肯定不再是指针了。那就继续看v.Elem()到底在干什么吧。先直接看答案吧
func (v Value) Elem() Value {
k := v.kind()
switch k {
case Interface:
....
case Ptr:
ptr := v.ptr
if v.flag&flagIndir != 0 {
ptr = *(*unsafe.Pointer)(ptr)
}
// The returned value's address is v's value.
if ptr == nil {
return Value{}
}
tt := (*ptrType)(unsafe.Pointer(v.typ))
typ := tt.elem
fl := v.flag&flagRO | flagIndir | flagAddr
fl |= flag(typ.Kind())
return Value{typ, ptr, fl}
}
panic(&ValueError{"reflect.Value.Elem", v.kind()})
}
v.Elem()虽然解索引了,但是Value的flag字段仍然将flagAddr 位置了1。指针仍然有效,这也是为啥传入[]byte不行了。
参考
[1] github.com/guonaihong/gout