As noted before, you can do this using interfaces. You can create a list of type []Intf
where Intf
is an interface common to your types, and then you can put any type that implements that interface into your list.
type A struct{}
type B struct{}
type Intf interface {
// common methods of A and B
}
func f(a Intf) {
// Here, a is A or B
}
You have to be careful about whether you're dealing with copies, or pointers. For instance:
a:=A{}
b:=B{}
x:=[]Intf{&a,&b}
f(x)
is different from:
x:=[]Intf{a,b}
f(x)
With the first one, if f
modified the array elements, the variables a
and b
are modified because the interface contains a pointer. With the second one, if f
modifies the array elements, a
and b
are not affected because the interfaces in the array contain pointers to copies of those variables.
If, say, A
has a method included in the interface Intf
that takes a pointer receiver, then you have to use the address of all A
instances in the array.
Go's type system is explicit, and strict. So, for instance, you implemented your polymorphic list via interfaces, and you have functions:
func f(input []Intf) {
}
func g(input Intf) {
}
And let's say you have these variables:
var a []A
var b A
where A
implements Intf
. Then:
g(b) // This is valid
f(a) // This is *not* valid
This is because f
gets a []Intf
, but a
is an []A
, not []Intf
. To call f
, you have to build a []Intf
:
fInput:=make([]Intf,0,len(a))
for _,x:=range a {
fInput=append(fInput,x)
}
f(fInput)
So if you're dealing with polymorphic lists, then it makes sense to always work with the interface list, not the concrete type list.
You can also work with type assertions if you need direct access to fields depending on type:
func f(in []Intf) {
for _,x:=range in {
if a, ok:=x.(*A); ok {
a.Field=1
}
}
}