28. What an interface is
An interface is a list of method names. Any type that has those methods satisfies the interface — automatically, with no declaration anywhere. That one idea is the centre of Go's design.
Declaring one
package main
import "fmt"
type Shape interface {
Area() float64
Perimeter() float64
}
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 { return r.Width * r.Height }
func (r Rectangle) Perimeter() float64 { return 2 * (r.Width + r.Height) }
func main() {
var s Shape = Rectangle{Width: 3, Height: 4}
fmt.Println(s.Area())
fmt.Println(s.Perimeter())
}
Shape says: anything with an Area() float64 and a Perimeter() float64
is a Shape. Rectangle has both, so it is one.
Look at Rectangle again: it never mentions Shape. No implements, no
: Shape, no registration. You could delete the Shape interface entirely
and Rectangle wouldn't change. This is structural typing, and Go
programmers call it implicit satisfaction.
Why implicit matters
In Java or C#, a type must be declared as implementing an interface — so the interface has to exist before the type, and it usually lives with the implementation. In Go the interface can be written afterwards, by somebody else, in a different package.
That inverts who owns the abstraction. The consumer defines the
interface, listing only the methods it actually needs, and every existing
type that happens to have them fits. You can write an interface today that
time.Time or a third-party library type already satisfies.
One interface, several types
package main
import (
"fmt"
"math"
)
type Shape interface {
Area() float64
Perimeter() float64
}
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 { return r.Width * r.Height }
func (r Rectangle) Perimeter() float64 { return 2 * (r.Width + r.Height) }
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius }
func (c Circle) Perimeter() float64 { return 2 * math.Pi * c.Radius }
func describe(s Shape) {
fmt.Printf("area %.2f, perimeter %.2f\n", s.Area(), s.Perimeter())
}
func main() {
shapes := []Shape{
Rectangle{Width: 3, Height: 4},
Circle{Radius: 5},
Rectangle{Width: 1, Height: 1},
}
total := 0.0
for _, s := range shapes {
describe(s)
total += s.Area()
}
fmt.Printf("total area %.2f\n", total)
}
describe works on anything shaped like a Shape. A []Shape can hold a
Rectangle and a Circle side by side, because what's stored is the
interface value, not the concrete type.
This is Go's polymorphism. No base class, no hierarchy — just "does it have the methods".
Interface values hold two things
An interface value is a pair: the concrete type and the value.
package main
import "fmt"
type Speaker interface {
Speak() string
}
type Dog struct{ Name string }
type Robot struct{ ID int }
func (d Dog) Speak() string { return d.Name + " says woof" }
func (r Robot) Speak() string { return fmt.Sprintf("unit %d reporting", r.ID) }
func main() {
var s Speaker
fmt.Printf("%v %T\n", s, s)
s = Dog{Name: "Rex"}
fmt.Printf("%v %T -> %s\n", s, s, s.Speak())
s = Robot{ID: 7}
fmt.Printf("%v %T -> %s\n", s, s, s.Speak())
}
%T prints the concrete type hiding inside the interface. A Speaker that
has never been assigned is nil and has no type — calling Speak() on it
would panic.
(One in-browser quirk: the interpreter running these boxes reports the
struct's shape — struct { Name string } — where a compiled Go binary
prints the type's name, main.Dog. The value and the dispatch are identical;
only the label differs.)
That pairing is what makes the method call work: at runtime, Go looks at the concrete type in the interface value and dispatches to its method. This is the "virtual dispatch" that embedding (module 5) deliberately doesn't do.
Keep interfaces small
The most-quoted line in Go: the bigger the interface, the weaker the abstraction. The standard library's most useful interfaces have one method:
type Stringer interface { String() string }
type error interface { Error() string }
type Writer interface { Write(p []byte) (n int, err error) }
type Reader interface { Read(p []byte) (n int, err error) }
A one-method interface is trivial to satisfy, trivial to fake in a test, and composes with everything. When you find yourself writing an interface with eight methods, you're probably describing a type rather than a requirement.
Accept interfaces, return structs
The other slogan worth internalising:
package main
import "fmt"
type Notifier interface {
Notify(msg string) string
}
type Email struct{ To string }
type SMS struct{ Number string }
func (e Email) Notify(msg string) string { return "email to " + e.To + ": " + msg }
func (s SMS) Notify(msg string) string { return "sms to " + s.Number + ": " + msg }
func alertAll(msg string, targets ...Notifier) {
for _, t := range targets {
fmt.Println(t.Notify(msg))
}
}
func main() {
alertAll("deploy finished",
Email{To: "ops@example.com"},
SMS{Number: "+15550100"},
)
}
alertAll takes an interface, so callers can pass anything that notifies —
including a fake one in a test. Constructors, on the other hand, return
concrete types (*Email), so callers keep access to everything the type
offers and can decide for themselves what interface it fits.
Take a parameter as an interface only when you'd genuinely accept more than one implementation. Interfaces "just in case" are a common way to make Go code harder to read for no benefit.
Your turn
Define an Animal interface with a Sound() string method, implement it for
Cow and Duck, and loop over a slice of Animal printing each sound:
moo
quack
package main
import "fmt"
// define the Animal interface, Cow and Duck
func main() {
animals := []Animal{Cow{}, Duck{}}
for _, a := range animals {
fmt.Println(a.Sound())
}
}
package main
import "fmt"
type Animal interface {
Sound() string
}
type Cow struct{}
type Duck struct{}
func (c Cow) Sound() string { return "moo" }
func (d Duck) Sound() string { return "quack" }
func main() {
animals := []Animal{Cow{}, Duck{}}
for _, a := range animals {
fmt.Println(a.Sound())
}
}
Next: the rules of satisfaction — including the pointer-receiver detail that trips everyone up once.