gogenericstypescastingtype-assertion

What's the correct way of returning one of different result values in one func in Golang?


I'm new to Go and was hoping to get some insights from more expirienced devs.

In my main function I call a 'Prepare' function that takes an enum as an argument. The enum is one of three options: A, B, C. Inside function 'PrepareData' I a have a switch case that calls one of three preparation methods of a corresponding struct, based on the enum type. Each of these methods returns a different struct as a result (typeA, typeB, typeC). But what should be 'Prepare' functions return value?

What's the cleanest ideomatic way to do this? I know I can return an emty interface from 'Prepare' function, or create an interface with a common method and return it, but these options don't seem "clean" to me. Am I wrong? I would like to keep the strong typing as much as possible and have a concrete type in the result of 'Prepare' func. Could generics or type asserton/casting be a solution here? Any suggestions would be greatly appreciated.

Here's some code for example:

type DataType int

const (
    A DataType = iota
    B
    C
)
func main() {
    dataType := C // Type will change in run time. Just an examlpe
    result := Prepare(dataType)
    fmt.Println(result)
}
func Prepare(dataType DataType) interface{} { // return value in the question
    switch dataType {
    case A:
        return TypeA{}.Prepare()
    case B:
        return TypeB{}.Prepare()
    case C:
        return TypeC{}.Prepare()
    default:
        return nil
    }
}
type TypeA struct{}

func (t TypeA) Prepare() *TypeA {
    return &TypeA{}
}
type TypeB struct{}

func (t TypeB) Prepare() *TypeB {
    return &TypeB{}
}
type TypeC struct{}

func (t TypeC) Prepare() *TypeC {
    return &TypeC{}
}

Solution

  • Go is no TypeScript, it's means for flexible typing are rather limited. Your options are:

    type Result struct {
      ResultA *TypeA
      ResultB *TypeB
      ResultC *TypeC
    }
    

    I'ld personally go with three dedicated functions or maybe with interface{}/any. Note that if you opt for the latter, there is a rather straight-forward pattern to handle the different expected types with type switches:

    switch v:=result.(type) {
    case TypeA, *TypeA:
        v.SpecificTypeAFunction()
    case TypeV, *TypeB:
        v.SpecificTypeBFunction()
    case TypeC, *TypeC:
        v.SpecificTypeCFunction()
    default:
        panic(fmt.Errorf("result is of type %T, which is not supported yet", result))
    }