One call, many behaviours
Polymorphism (Greek for "many forms") means one piece of code does different things depending on the actual kind of value it is given. You write a.speak() once, and the right version runs for each value. In Go, polymorphism comes from interfaces.
Interfaces are satisfied implicitly
An interface is a list of method signatures:
type Animal interface {
speak()
}
Any type that has all the listed methods is an Animal. There is no implements keyword and no inheritance relationship to declare. Dog and Cat below are unrelated types, and each qualifies simply because it has a speak() method.
type Dog struct {}
func (this Dog) speak() {
fmt.Println("Woof")
}
type Cat struct {}
func (this Cat) speak() {
fmt.Println("Meow")
}
Using the interface
A variable of interface type can hold a value of any type that satisfies it. A slice of the interface type can mix them:
animals := []Animal{Dog{}, Cat{}}
for _, a := range animals {
a.speak()
}
This loop prints Woof and then Meow. The loop variable a has the type Animal, and the method that runs is chosen by the concrete type stored inside it. That choice happens while the program runs, which is called dynamic dispatch.
Static type and dynamic type
The static type of a is what the code says: Animal. Its dynamic type is what it holds at run time: Dog for the first element, Cat for the second. Printing with fmt.Printf("%T\n", a) shows the dynamic type, main.Dog and main.Cat in this program.
Why it matters
A function that takes []Animal and calls speak on each element works with every type that exists now and any that you add later. If you add a Cow type with a speak method, the loop needs no edits:
type Cow struct{}
func (c *Cow) speak() { fmt.Println("Moo") }
animals := []Animal{Dog{}, Cat{}, &Cow{}}
Notice &Cow{}. Because speak has a pointer receiver here, only a *Cow satisfies Animal. Putting a plain Cow{} in the slice is a compile error: "method speak has pointer receiver". The rule is that the method set of a value type contains only value-receiver methods, while the pointer type has both.
Getting the concrete type back
Sometimes you need to know which type an interface value holds. A type assertion with the two-value form checks it safely:
if d, ok := animals[0].(Dog); ok {
fmt.Println("the first animal is a Dog", d)
}
If the stored value is not a Dog, ok is false and nothing breaks. Reach for this sparingly: if you are checking types all the time, the interface probably needs another method instead.
The lesson's exercise
The Animal interface and the two types are already written. You write the code that uses them.
- Declare
mainFunctionwith no parameters and no result. - Build the slice with
animals := []Animal{Dog{}, Cat{}}. Each element is a concrete struct value wrapped in the interface. - Range over it, discarding the index with
_, and calla.speak()on each element.
type Animal interface {
speak()
}
type Dog struct {}
func (this Dog) speak() {
fmt.Println("Woof")
}
type Cat struct {}
func (this Cat) speak() {
fmt.Println("Meow")
}
func mainFunction() {
animals := []Animal{Dog{}, Cat{}}
for _, a := range animals {
a.speak()
}
}
The output is:
Woof
Meow
Common mistakes
- Expecting inheritance.
DogandCatdo not inherit fromAnimal. They satisfy it by having the method, which is checked by the compiler where a value is assigned to anAnimal. - Missing a method. A type that lacks
speakcannot go in the slice, and the compiler says which method is missing. - Mixing up pointer and value receivers, as described above.
- Calling a method on a nil interface. A variable declared as
var a Animalholds nothing, soa.speak()stops the program with a nil pointer panic. - Forgetting the underscore.
for i, a := range animalswithiunused does not compile. Write_for the index you ignore.
Key takeaways
- An interface lists methods, and any type with those methods satisfies it automatically.
- Calling a method on an interface value runs the method of the stored concrete type.
- That lets one loop handle current and future types without edits.
- Whether a value or a pointer satisfies an interface depends on the receiver types.