1

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

12
  • 1
    Why are you creating an instance with reflect then immediately overwriting the variable that contains it? Commented Apr 5, 2017 at 16:25
  • 1
    But you already have the base variable.... all you are doing is creating a new pointer that points to the same data. Is that what you are trying to accomplish? Commented Apr 5, 2017 at 16:35
  • 1
    I also can't figure out what exactly you're trying to accomplish, but does this get you closer? play.golang.org/p/n-G5YwYkaH Commented Apr 5, 2017 at 16:44
  • 1
    I can't quite figure out either, but in the code I c/p and modified, I get similar output, but missing method A. play.golang.org/p/860uAE7qLc Commented Apr 5, 2017 at 16:48
  • 1
    Updated @RayfenWindspear's link, play.golang.org/p/h1cl4FNUGY, is this what you want? Commented Apr 5, 2017 at 17:17

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

4 Comments

that is the solution, it is indeed traversing the fields of B and copying one by one. I did a small change which is to set the M field to dest and it works, I'll just add the "deep" copy because B will have subfields that are pointers.
I appreciated the time you put into it!!
I'm glad I could be of help!
Just as I note, I found a package that performs deep copy: github.com/ulule/deepcopier

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.