Search code examples
goreflectiongo-reflect

How to use "reflect" to set interface value inside a struct of struct


Had a rough time trying to set the interface value by using "reflect" package. The interface value is actually inside a struct of a struct. See my code in Go Playground

Basically, inside initProc, I want to assign dummyAFunc function to DummyA field in Box struct

package main 

import (
    "fmt"
    "reflect"
)

type Box struct {
    Name               string
    DummyA             interface{}
}

type SmartBox struct {
    Box
}

func dummyAFunc(i int) {
    fmt.Println("dummyAFunc() is here!")
}

func initProc(inout interface{}) {
    // Using "inout interface{}", I can take any struct that contains Box struct
    // And my goal is assign dummyAFunc to dummyA in Box struct

    iType:=reflect.TypeOf(inout)
    iValue:=reflect.ValueOf(inout)
    
    fmt.Println("Type & value:", iType.Elem(), iValue.Elem()) // Type & value: *main.SmartBox &{{ <nil>}}

    e := reflect.ValueOf(inout).Elem()
    
    fmt.Println("Can set?", e.CanSet()).      // true
    fmt.Println("NumField", e.NumField())     // panic: reflect: call of reflect.Value.NumField on ptr Value ?????
    fmt.Println("NumMethod", e.NumMethod())   // NumMethod = 0
        
}

func main() {
    smartbox := new (SmartBox)
    initProc(&smartbox)
}

I'm new to Go and I've read the The laws of Reflection but still can't figure it out. Please help. Thanks!


Solution

  • You are passing a **SmartBix to initProc. So when you dereference once with reflect's Elem() you are still getting a pointer (*Smart box).

    Since new already returns a pointer, just use:

    smartbox := new (SmartBox)
    
    // InitProc(smartbox) // **SmartBox
    InitProc(smartbox) // *SmartBox
    

    https://play.golang.org/p/j4q6aq6QL_4


    EDIT

    To update the input struct's DummyA field, you can do something like this:

    func initProc2(v interface{}) error {
    
        if reflect.TypeOf(v).Kind() != reflect.Ptr {
            return fmt.Errorf("value must be a pointer")
        }
    
        dv := reflect.ValueOf(v).Elem()
    
        if dv.Kind() != reflect.Struct {
            return fmt.Errorf("value must be a pointer to a struct/interface")
        }
    
        const fname = "DummyA" // lookup field name
    
        f := dv.FieldByName(fname)
    
        if !f.CanSet() {
            return fmt.Errorf("value has no field %q or cannot be set", fname)
        }
    
        nv := reflect.ValueOf(dummyAFunc)
    
        f.Set(nv)
    
        return nil
    }
    

    Working example: https://play.golang.org/p/VE751GtSGEw