Tristian
Tristian

Reputation: 3512

How can I convert struct value into struct pointer using reflection

The goal is

Having a variable B that satisfies a specific interface I through pointer receivers type, create another variable C (with reflection and using B), copy B's values into C, modify C (without changing B) and return C as type I.

Suppose that I have the following types, the following snippets mimic production code:

import (
    "reflect"
)

type IFace interface {
    A() 
    B()
    C()
}

type Meta struct {
    s string
}

func (m *Meta) A() {}
func (m *Meta) B() {}
func (m *Meta) C() {}

type One struct {
    M *Meta
    B bool
}

func (o *One) A() {}
func (o *One) B() {}
func (o *One) C() {}

And I have a method that does the following:

func Alias(src, dest *Meta) (IFace, error) {
    base, err := find(src) //asume that `find` is implemented and err is nil
    if err != nil { 
       return err
    }

    // trouble starts here ...
    // allocate new "instance"
    aliased := reflect.New(reflect.TypeOf(base)).Elem().Interface()

    // copy the base value
    aliased = base 

    aliasedV := reflect.ValueOf(aliased).Elem()
    fm := aliasedV.FieldByName("M")
    fm.Set(reflect.ValueOf(dest))

    return aliasedV.Interface().(Iface), nil
}

It compiles and runs however with the following TestFunction it gives me this error message:

interface conversion: One is not IFace: missing method C [recovered]
    panic: interface conversion: One is not IFace: missing method C

and the test function:

func TestOne(t *testing.T) {
   srcID := &Meta{S: "SRC"}
   destID := &Meta{S: "DEST"}
   aliased, err := Alias(srcID, destID)
   if err != nil {
       t.Error(err)
   }

   one, isOne := aliased.(*One)
   if !isOne {
       t.Error("fail")
   }
}

Is there a way to have an interface{} type that wraps a struct value become an interface{} type that wraps a struct pointer without using the underlying struct type directly, like avoiding: var any interface{} = aliased.(*One) ??,

Could the unsafe package be of help here?

Here is a playground that replicates the panic, thanks to RayfenWindspear

https://play.golang.org/p/860uAE7qLc

Upvotes: 1

Views: 1849

Answers (1)

mkopriva
mkopriva

Reputation: 38203

Not sure if this is what you want, but if I understood you correctly, in your updated example from the comments the returned base is what you want to copy, modify, and then return the copy of, without changing anything in base. If that's the case, this code aliased = base is not gonna do what you want if the base's underlying type is a pointer, which is true in this case.

Note, I've modified the var names to better reflect your assignment.

// Having a variable B that satisfies a specific
// interface I through pointer receivers type.
B, err := find(src)
if err != nil {
    return nil, err
}

// create another variable C (with reflection and using B),
C := reflect.New(reflect.TypeOf(B).Elem())

// copy B's values into C
bv := reflect.ValueOf(B).Elem()
for i :=0; i < bv.NumField(); i++ {
    fv := bv.Field(i)
    if fv.Kind() == reflect.Ptr {
        v := reflect.New(fv.Elem().Type()) // allocate a new pointer of the same type as the B's pointer field is pointing to, in this case 'Meta'
        v.Elem().Set(fv.Elem()) // set the newly allocated pointer's value to the same value as B's pointer field is pointing to, in this case 'Meta{S: "SRC"}'
        C.Elem().Field(i).Set(v) // set the newly allocated pointer as the C's field
    } else {
        C.Elem().Field(i).Set(fv) // non pointer field? just set and that's it
    }
    // NOTE: if B's field's have subfields that are pointers you'll have to do this for all of them if you want a deep copy
}

// modify C (without changing B)
C.Elem().FieldByName("M").Elem().FieldByName("S").Set(reflect.ValueOf("Hello, 世界"))

Here's the playground: https://play.golang.org/p/bGTdy2vYUu

Sorry if this not what you're looking for.

Upvotes: 1

Related Questions