I'm trying to write code that recursively traverses a struct and keeps track of pointers to all its fields to do basic analysis (size, number of references, etc). However, I'm running into an issue where I can't seem to get reflection to give me the pointer to a pure struct. I have the following code as an example:
type foo struct {
A *bar
data []int8
}
type bar struct {
B *foo
ptrData *[]float64
}
func main() {
dataLen := 32
refData := make([]float64, dataLen)
fooObj := foo{data: make([]int8, dataLen)}
barObj := bar{
B: &fooObj,
ptrData: &refData,
}
fooObj.A = &barObj
fooVal := reflect.ValueOf(fooObj)
_ := fooVal.Addr().Pointer() // fails
_ := fooVal.Pointer() // fails
// More analysis code after this
}
If I wanted to traverse fooObj, that would be fine until I entered barObj at which point I again encounter fooObj. Because I don't have a way to get the pointer for the initial fooObj encounter, I end up traversing fooObj twice until I hit barObj the second time and exit the recursion. Any idea how to get a struct's pointer using reflection?
package main
import (
"reflect"
"fmt"
)
type foo struct {
A *bar
data []int8
}
type bar struct {
B *foo
ptrData *[]float64
}
func main() {
dataLen := 32
refData := make([]float64, dataLen)
// allocate here, now value has a pointer
fooObj := &foo{data: make([]int8, dataLen)}
barObj := bar{
B: fooObj,
ptrData: &refData,
}
fooObj.A = &barObj
fooVal := reflect.ValueOf(fooObj)
fmt.Println(fooVal.Pointer()) // succeeds
// More analysis code after this
}
Addr returns a pointer value representing the address of v. It panics if CanAddr() returns false. Addr is typically used to obtain a pointer to a struct field or slice element in order to call a method that requires a pointer receiver.
This takes a pointer of a value if it's possible.
package main
import "reflect"
import "fmt"
func main() {
val := new(int)
slice := []int{}
local := 10
fn := func() {}
fmt.Println(PointerOf(val))
fmt.Println(PointerOf(slice))
fmt.Println(PointerOf(&local))
fmt.Println(PointerOf(fn))
fmt.Println(PointerOf(3))
}
func PointerOf(value any) (p uintptr, ok bool) {
rValue := reflect.ValueOf(value)
if(rValue.Kind() == reflect.Pointer || rValue.Kind() == reflect.Slice || rValue.Kind() == reflect.Func) {
return rValue.Pointer(), true
}
if(rValue.CanAddr()) {
return rValue.Addr().Pointer(), true
}
return
}
As you mentioned, your code fails in this part
fooVal := reflect.ValueOf(fooObj)
_ = fooVal.Addr().Pointer() // fails
_ = fooVal.Pointer() // fails
In foo struct data is not a pointer value.
type foo struct {
A *bar
data []int8
}
You are trying to call reflect.Value.Addr().Pointer() and reflect.Value.Pointer() on a non-pointer type, causing the error.
You can prevent this condition by checking if the type is actually ptr
package main
import (
"reflect"
)
type foo struct {
A *bar
data []int8
}
type bar struct {
B *foo
ptrData *[]float64
}
func main() {
dataLen := 32
refData := make([]float64, dataLen)
fooObj := foo{data: make([]int8, dataLen)}
barObj := bar{
B: &fooObj,
ptrData: &refData,
}
fooObj.A = &barObj
fooVal := reflect.ValueOf(fooObj)
if fooVal.Kind().String() == "ptr" {
_ = fooVal.Addr().Pointer() // ok
_ = fooVal.Pointer() // ok
// More analysis code for pointer types
} else {
// More analysis code for non-pointer types
}
}
Related
I want to manipulate various structs' field which has same name and type programmatically like the following but I have no idea how to put varied structs in a list.
package main
import "fmt"
type A struct {
Cnt int
}
type B struct {
Cnt int
}
func main() {
a := &A{}
b := &B{}
list := []something{
a,
b,
}
for _, item := range list {
item.Cnt++
}
fmt.Printf("a.Cnt: %d, b.Cnt: %d", a.Cnt, b.Cnt)
}
Declare a common interface for the types. The methods should reflect whatever action you want to executed on the values. I use add here as generalization of increment.
type Cntr interface {
Add(i int)
}
Implement that interface on each type:
func (a *A) Add(i int) { a.Cnt += i }
func (b *B) Add(i int) { b.Cnt += i }
Declare slice of interface type and with values of types *A and *B:
a := &A{}
b := &B{}
list := []Cntr{ // <-- slice of the interface type
a,
b,
}
Increment the counters:
for _, item := range list {
item.Add(1)
}
Print the results:
fmt.Printf("a.Cnt: %d, b.Cnt: %d", a.Cnt, b.Cnt)
// prints a.Cnt: 1, b.Cnt: 1
Run this playground on the program.
Use the reflect API to get a pointer to a named field in an arbitrary struct type:
func getFieldPtr(v interface{}, name string) interface{} {
return reflect.ValueOf(v).Elem().FieldByName(name).Addr().Interface()
}
Use it like this:
a := &A{}
b := &B{}
list := []interface{}{
a,
b,
}
for _, item := range list {
pcnt := getFieldPtr(item, "Cnt").(*int)
*pcnt++
}
fmt.Printf("a.Cnt: %d, b.Cnt: %d", a.Cnt, b.Cnt)
https://go.dev/play/p/InVlnv37yqW
I can't explain why the following is working.
package main
import (
"fmt"
"reflect"
"strings"
)
type MyInterface interface {
someFunc()
}
type Dock struct {
}
func (d *Dock) someFunc() {
}
type Group struct {
Docks []Dock `better:"sometag"`
}
func foo(model interface{}) {
v1 := reflect.Indirect(reflect.ValueOf(model))
for i := 0; i < v1.NumField(); i++ {
tag := v1.Type().Field(i).Tag.Get("better")
if strings.HasPrefix(tag, "sometag") {
inter := v1.Field(i).Interface()
typ := reflect.TypeOf(inter).Elem()
fmt.Println("Type:", typ.String())
// Want to instantiate type like &Dock{} then assign it to some interface,
// but using reflect
n := reflect.New(typ)
_, ok := n.Interface().(MyInterface)
fmt.Println("Why is it OK?", ok)
}
}
}
func main() {
g := &Group{}
foo(g)
/*var v1, v2 interface{}
d1 := &Dock{}
v1 = d1
_, ok1 := v1.(MyInterface)
d2 := Dock{}
v2 = d2
_, ok2 := v2.(MyInterface)
fmt.Println(ok1, ok2)*/
}
It prints
Type: main.Dock
OK? true
If it's a Dock type, then it's not a pointer to Dock. Why does it conforms to MyInterface?
https://play.golang.org/p/Z9mR8amYOM7
Where as the d2 example in the comment does not.
In go doc for reflect.New
New returns a Value representing a pointer to a new zero value for the
specified type. That is, the returned Value's Type is PtrTo(typ).
n := reflect.New(typ)
fmt.Println("Type:", n.String())
It will print Type: <*main.Dock Value> means n is a pointer of Dock.You miss the part using reflect.New return the pointer.
I write the code that fills data structures depending on its type. I need to call nested struct function if it exists.
Why I get zero value looking for function while the field is correct?
type (
SomeData struct {
Val NestedType
}
NestedType struct {
V1 string
}
)
func (t *NestedType) FillData(v int) {
t.V1 = fmt.Sprintf("Here is %v", v)
}
func main() {
i := SomeData{}
reflect.ValueOf(&i.Val).MethodByName("FillData").Call([]reflect.Value{reflect.ValueOf(555)})
fmt.Println(i) /// {{I hate 555}}
// BUT!
v := 666
newObj := reflect.New(reflect.TypeOf(SomeData{}))
fVal := newObj.Elem().FieldByName("Val")
fmt.Println( "fVal.NumField():", fVal.NumField()) //fVal.NumField(): 1
f := fVal.MethodByName("FillData")
f.Call([]reflect.Value{reflect.ValueOf(v)}) //panic: reflect: call of reflect.Value.Call on zero Value
}
The method is on the pointer receiver. The value fVal is a NestedType. Call Value.Addr to get a *NestedType:
f := fVal.Addr().MethodByName("FillData")
Run it on the playground.
I am trying to copy the fields from one struct value to another, where they have the same field definitions. I have this program:
package main
import (
"log"
"reflect"
)
func setExistingFields(src interface{}, dst interface{}) {
fields := reflect.TypeOf(src)
values := reflect.ValueOf(src)
num := fields.NumField()
s := reflect.ValueOf(src).Elem()
d := reflect.ValueOf(dst).Elem()
for i := 0; i < num; i++ {
field := fields.Field(i)
value := values.Field(i)
fsrc := s.FieldByName(field.Name)
fdest := d.FieldByName(field.Name)
if fdest.IsValid() && fsrc.IsValid() {
if fdest.CanSet() && fsrc.CanSet() {
fdest.Set(value)
}
}
}
}
// and then we main:
func main() {
src := struct {
Foo string
Bar string
}{
"dog",
"pony",
}
dest := struct{ Foo string; Bar string }{}
setExistingFields(&src, &dest)
log.Println("dest.Foo", dest.Foo)
}
I run that, but I get an error:
reflect: NumField of non-struct type
I can't figure out what that's about.
Here's a playground link:
https://play.golang.org/p/TsHTfAaeKhc
Try this out:
func setExistingFields(src interface{}, dst interface{}) {
srcFields := reflect.TypeOf(src).Elem()
srcValues := reflect.ValueOf(src).Elem()
dstValues := reflect.ValueOf(dst).Elem()
for i := 0; i < srcFields.NumField(); i++ {
srcField := srcFields.Field(i)
srcValue := srcValues.Field(i)
dstValue := dstValues.FieldByName(srcField.Name)
if dstValue.IsValid() {
if dstValue.CanSet() {
dstValue.Set(srcValue)
}
}
}
}
Note that you need to do additional checking if src field value is assignable to dst field type.
Edit: The reason why you are getting that error is because fields at that point is a pointer to a struct. You need to get the actual struct value by using Elem().
This won't work: A struct always gets its "schema" (eg. its fields) during compile time... You cannot add more fields during runtime.
I don't see what your exact use case is, but consider something like map[string]string or even map[string]interface{} to be able to "extend" the content/fields of the thing you are passing around...
I am trying to use reflect to set a pointer to a struct to nil passed in through an interface{} in Go.
I have this example program but it always prints false as a is not set to nil. What am I doing wrong?
package main
import "reflect"
type MyStruct struct {}
func main() {
a := &MyStruct{}
wipePassed(a)
println(a == nil)
}
func wipePassed(r interface{}){
v := reflect.ValueOf(&r)
p := v.Elem()
p.Set(reflect.Zero(p.Type()))
}
You can't with your current code. The pointer a is passed (as if) by value even though it's being wrapped in an interface, and there's no way to zero the original copy. This is just the same as the case when a is a non-pointer type (for example an int); there's no function f that can change the value of a in the code a := 42; f(a) -- you have to pass a pointer.
Your code can work if you pass in the address of the pointer a:
package main
import "reflect"
type MyStruct struct{}
func main() {
a := &MyStruct{}
wipePassed(&a)
println(a == nil)
}
func wipePassed(r interface{}) {
v := reflect.ValueOf(r)
p := v.Elem()
p.Set(reflect.Zero(p.Type()))
}