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{}
}
Go is no TypeScript, it's means for flexible typing are rather limited. Your options are:
interface{}
/any
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
}
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))
}