Search code examples
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:

    • Return three different, dedicated functions, as already suggestions in the comments by @Volker. This is the only "real" type safety you can get out of Go. And I don't really see what advantage a single function has which behaves completely different depending on its input value. If there's shared code, extract it to a separate utility function.
    • Return interface{}/any
    • Return a struct like the one below, where you can check later which value isn't nil. This is rather bloated.
    type Result struct {
      ResultA *TypeA
      ResultB *TypeB
      ResultC *TypeC
    }
    
    • If it's just three different types (and won't become much more), return them in a manner of func Prepare(dataType DataType) (*TypeA, *TypeB, *TypeC) {..., and check which is not nil.

    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))
    }