Home > database >  How to make an addressable getter that casts from an interface
How to make an addressable getter that casts from an interface

Time:10-12

I have the concept of Context which is a map that can hold any structure. Basically, I want to create a generic getter that adddressably 'populates' the destination interface (similarly to how json decoding works).

Here's an example of how I want this to work:

type Context map[string]interface{}

// Random struct that will be saved in the context
type Step struct { 
    Name string
}

func main() {
    stepA := &Step{Name: "Cool Name"}

    c := Context{}
    c["stepA"] = stepA

    var stepB *Step
    err := c.Get("stepA", stepB)
    if err != nil {
        panic(err)
    }

    fmt.Println(stepB.Name) // Cool Name
    stepB.Name = "CoolName2"

    fmt.Println(stepA.Name) // I want to say: CoolName2
}

func (c Context) Get(stepId string, dest interface{}) error {
    context, ok := c[stepId]
    if !ok {
        return nil
    }

    destinationValue := reflect.ValueOf(dest)
    contextValue := reflect.ValueOf(context)

    destinationValue.Set(contextValue) // Errors here
    return nil
}

I leaned towards using reflect, but maybe I don't need it? - so opened to other suggestions (except for generics as that complicates other matters) I'm getting the following error with the above:

panic: reflect: reflect.Value.Set using unaddressable value

You can test it here.

CodePudding user response:

The argument passed to Get must be a pointer type whose element type is identical to the type in the context map. So if the value in the context map is of type *Step, then the argument's type must be **Step. Also the passed in argument cannot be nil, it can be a pointer to nil, but it itself cannot be nil.

So in your case you should do:

var stepB *Step
err := c.Get("stepA", &stepB) // pass pointer-to-pointer
if err != nil {
    panic(err)
}

And the Get method, fixed up a bit:

func (c Context) Get(stepId string, dest interface{}) error {
    context, ok := c[stepId]
    if !ok {
        return nil
    }

    dv := reflect.ValueOf(dest)
    if dv.Kind() != reflect.Ptr || dv.IsNil() {
        return errors.New("dest must be non-nil pointer")
    }

    dv = dv.Elem()
    cv := reflect.ValueOf(context)
    if dv.Type() != cv.Type() {
        return errors.New("dest type does not match context value type")
    }

    dv.Set(cv)
    return nil
}

https://go.dev/play/p/OECttqp1aVg

  •  Tags:  
  • go
  • Related