获取可寻址的 reflect.Value 副本

huangapple go评论79阅读模式
英文:

Obtaining an addressable copy of a reflect.Value

问题

我想要为我接受的类型添加编组/解组的功能,类似于 JSON 的自定义编码/解码方式。当类型实现了 Marshal 方法(值接收器)和 Unmarshal 方法(指针接收器)时,我已经成功实现了这个功能。

显然,Unmarshal 方法必须使用指针接收器,以便保存新值。有人告诉我,使用 指针(而不是值)接收器的 Marshal 方法也是可以的。但问题在于,我正在使用的 reflect.Value 并不总是可寻址的。

以下是代码的编辑摘录,会导致 panic:

var v reflect.Value // 作为参数传入

t := v.Type()
pt := reflect.TypeOf(reflect.New(t).Interface())

if t.Implements(reflect.TypeOf((*Marshaler)(nil)).Elem()) {
  str, err = v.Interface().(Marshaler).MarshalXXX()
  // ... 值接收器工作正常

} else if pt.Implements(reflect.TypeOf((*Marshaler)(nil)).Elem()) {
  str, err = v.Addr().Interface().(Marshaler).MarshalXXX()
  // 指针接收器导致 PANIC:值不可寻址

我尝试创建 v 的副本,但副本也是不可寻址的,这对我来说没有意义:

tmp := reflect.New(v.Type())
tmp.Addr().Set(v)
str, err = tmp.Interface().(Marshaler).MarshalXXX()

如果我没有解释清楚,这里有一个在 Go Playground 上的示例,你可以尝试一下。

还有,当我在 pt 的赋值语句中获取指向类型的指针时,是否有更好的方法?

英文:

I want to add marshalling/unmarshalling to types that I accept, in a way similar JSON custom encoding/decoding. I have it all working great when the type implements a Marshal method (value receiver) and Unmarshal method (pointer receiver).

The Unmarshal method must obviously use a pointer receiver so that the new value is saved. I have been told that a Marshal method using a pointer (not value) receiver should also be allowed. The trouble with this is I am using a reflect.Value which is not always addressable.

Here's an edited extract of the code which panics:

  var v reflect.Value // passed in parameter

  t := v.Type()
  pt := reflect.TypeOf(reflect.New(t).Interface())

  if t.Implements(reflect.TypeOf((*Marshaler)(nil)).Elem()) {
    str, err = v.Interface().(Marshaler).MarshalXXX()
    // ... value receiver WORKS FINE

  } else if pt.Implements(reflect.TypeOf((*Marshaler)(nil)).Elem()) {
    str, err = v.Addr().Interface().(Marshaler).MarshalXXX()
    // ptr receiver gives PANIC value is not addressable

I tried creating a copy of v, but the copy is also not addressable which makes no sense to me:

    tmp := reflect.New(v.Type())
    tmp.Addr().Set(v)
    str, err = tmp.Interface().(Marshaler).MarshalXXX()

In case I have not explained this well here is an example on the Go Playground for you to try:

Also while I am here: Is there a better way to get the type of a pointer to a the type than in the assignment to pt above?

答案1

得分: 4

我找到了答案。问题是我使用了Addr而不是Elem。尽管如此,这也引出了一个问题,为什么Set方法能够正常工作,因为恐慌错误直到下一行才发生。

总结一下,要创建一个可寻址的 reflect.Value v 的副本,请按照以下步骤进行:

tmp := reflect.New(v.Type()) // 创建与 v 相同类型的零值
tmp.Elem().Set(v)

请原谅我过早地提问,但我已经花了4个小时来解决这个问题。为了完整起见,这里是我在问题中修复的 Go Playground 代码

英文:

I found the answer. The problem was I was using Addr instead of Elem. Though that begs the question why Set worked, since the panic did not occur till the following line.

In summary, to create an addressable copy of a reflect.Value v do this:

  tmp := reflect.New(v.Type()) // create zero value of same type as v
  tmp.Elem().Set(v)

Please forgive me asking prematurely, but I had already spent 4 hours trying to solve the problem. For completeness here is the fix to my Go playground code in the question.

huangapple
  • 本文由 发表于 2022年4月11日 21:33:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/71828776.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定