How to get union typing in golang for shared fields?

Viewed 39

I have a few classes that I'm importing that have some common fields.

package foo

type FooA struct {
    ...
    SharedVal int
}

type FooB struct {
    ...
    SharedVal int
}

type FooC struct {
    ...
    SharedVal int
}

For various reasons, I am unable to modify the foo package. I want to have some abstraction that gives me some sort of union typing so that I can create a setter for the sharedVal field; e.g. something like this

package bar

type GenericFoo = foo.FooA | foo.FooB | foo.FooC

func (f *GenericFoo) SetSharedVal(val int) {
    f.SharedVal = val
}

How do I do this? Interfaces don't work for me since they required shared methods, but the Foo classes don't have setters. It's preferrable that the solution doesn't involve importing other third-party packages since dependency management becomes complicated in my use case.

1 Answers

Given the constraint that you cannot modify the third-party package and there is no common interface method to leverage, your only alternative is to use the reflect package to perform runtime inspection of any input values.

One such solution:

import "reflect"

// setSharedVal takes a pointer to a struct with a field 'SharedVal' of type int and sets it
func setSharedVal(v any, nv int) error {

    var rv reflect.Value

    if rv = reflect.ValueOf(v); rv.Kind() != reflect.Ptr {
        return fmt.Errorf("must pass a pointer to struct")
    }

    if rv = rv.Elem(); rv.Kind() != reflect.Struct {
        return fmt.Errorf("must pass a pointer to struct")
    }

    const fieldName = "SharedVal"
    rv = rv.FieldByName(fieldName)

    switch rv.Kind() {
    case reflect.Invalid:
        return fmt.Errorf("struct has no field named %q", fieldName)

    case reflect.Int:
        rv.SetInt(int64(nv))
        return nil

    default:
        return fmt.Errorf("struct field %q must be of type %q", fieldName, reflect.Int)
    }
}

a, b, c := FooA{}, FooB{}, FooC{}

err = setSharedVal(&a, 1)
err = setSharedVal(&b, 2)
err = setSharedVal(&c, 3)

Output:

main.FooA{SharedVal:1}
main.FooB{SharedVal:2}
main.FooC{SharedVal:3}

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

Related