An interface in Go is a set of method signatures. Any type that has those methods automatically satisfies the interface β you describe what a value can do, not what it is.
Declaring an interface
You declare an interface with type ... interface and list the methods it
requires. By convention, single-method interfaces often end in -er.
// Shape is anything that can report its area.
type Shape interface {
Area() float64
}
Implicit satisfaction
There is no implements keyword in Go. A type satisfies an interface simply by
having the required methods. This decoupling means a type can satisfy an
interface that was defined later, even in another package.
type Circle struct {
Radius float64
}
// Because Circle has an Area() float64 method, it IS a Shape.
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
A complete example
The program below stores different concrete types in a []Shape and computes a
total without caring which shape is which. It compiles and runs as-is.
package main
import (
"fmt"
"math"
)
// Shape is anything that can report its area.
type Shape interface {
Area() float64
}
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
// totalArea works with any Shape, whatever its concrete type.
func totalArea(shapes []Shape) float64 {
var sum float64
for _, s := range shapes {
sum += s.Area()
}
return sum
}
func main() {
shapes := []Shape{
Rectangle{Width: 3, Height: 4},
Circle{Radius: 5},
}
fmt.Printf("total area: %.2f\n", totalArea(shapes))
}
Running it prints:
total area: 90.54
The empty interface and any
An interface with no methods is satisfied by every type. It is written
interface{}, but since Go 1.18 the built-in alias any means the same thing
and reads far better.
var x any = 42 // holds an int
x = "now a string" // holds a string
x = []int{1, 2, 3} // holds a slice
Use any sparingly: it throws away type information, so you get no compile-time
checking. Prefer a specific interface whenever you can.
Type assertions
To recover the concrete value stored in an interface, use a type assertion. The
comma-ok form is safe: a wrong guess sets ok to false instead of panicking.
var i any = "hello"
s := i.(string) // s == "hello"
n, ok := i.(int) // ok == false, n == 0 (no panic)
if s2, ok := i.(string); ok {
fmt.Println("it is a string:", s2)
}
The Stringer interface
The standard library defines fmt.Stringer, an interface with a single
String() string method. fmt looks for it automatically, so implementing it
controls how your type prints.
package main
import "fmt"
// Point implements fmt.Stringer by defining a String method.
type Point struct {
X, Y int
}
func (p Point) String() string {
return fmt.Sprintf("(%d, %d)", p.X, p.Y)
}
func main() {
p := Point{X: 1, Y: 2}
fmt.Println(p) // fmt uses String() automatically
}
This prints (1, 2).
Why interfaces matter
Because satisfaction is implicit, a function that accepts an interface does not depend on any concrete type. That decoupling is what makes interfaces so useful for testing: in a test you pass a small fake that satisfies the interface, instead of the real database or network client.
Common mistakes
- Over-large interfaces. A five-method interface is hard to satisfy and to fake. Split it, or use single-method interfaces.
- Reaching for
anytoo soon. It disables type checking. Model behaviour with a real interface instead. - Panicking type assertions. The single-value form
i.(int)panics on a wrong guess. Use the comma-ok form unless you are certain. - A nil interface that is not nil. An interface holding a nil pointer is
itself non-nil, which surprises many newcomers when checking
err != nil.
Practice
Define a Greeter interface with a Greet() string method, implement it with a
Person struct, and print the greeting through the interface.
Show the solution
package main
import "fmt"
// Greeter is satisfied by any type with a Greet method.
type Greeter interface {
Greet() string
}
type Person struct {
Name string
}
// Person satisfies Greeter implicitly: no "implements" keyword.
func (p Person) Greet() string {
return "Hello, my name is " + p.Name
}
func main() {
var g Greeter = Person{Name: "Ada"}
fmt.Println(g.Greet())
}Person never names Greeter, yet it satisfies it just by having the right
method. The variable g has the interface type, so the call goes through the
interface. The program prints Hello, my name is Ada.
Summary
- An interface is a set of methods; any type with those methods satisfies it.
- Satisfaction is implicit β there is no
implementskeyword. any(the empty interface) holds any value; recover it with a type assertion.- Small interfaces decouple code and make it easy to test.
Next, see how the error interface underpins
error handling in Go. To revisit the types that
implement interfaces, review
structs and methods.