Kodewick
Start learning →
Lesson 13 of 24Contents

Interfaces

Lesson 13 of 24 · Part 3 · Types · about 20 minutes

Rectangles and circles both have an area, worked out in different ways. An interface describes what a value can do: it is a set of methods. The Shape below asks for one method, Area() float64, and describe takes any value that has it:

prog.go
1package main2 3import (4	"fmt"5	"math"6)7 8type Shape interface {9	Area() float6410}11 12type Rect struct {13	Width, Height float6414}15 16func (r Rect) Area() float64 {17	return r.Width * r.Height18}19 20type Circle struct {21	Radius float6422}23 24func (c Circle) Area() float64 {25	return math.Pi * c.Radius * c.Radius26}27 28func describe(s Shape) {29	fmt.Printf("Area: %.2f\n", s.Area())30}31 32func main() {33	describe(Rect{Width: 3, Height: 4})34	describe(Circle{Radius: 1})35}
Output
Area: 12.00
Area: 3.14

describe doesn't know or care whether it got a rectangle or a circle. It just calls Area.

No implements needed

Rect and Circle never say "I am a Shape". In Go, a type that has all the methods an interface asks for implements that interface automatically; there's no implements keyword.

A variable of an interface type can hold any value that implements it, and what it holds can change:

prog.go
1package main2 3import (4	"fmt"5	"math"6)7 8type Shape interface {9	Area() float6410}11 12type Rect struct {13	Width, Height float6414}15 16func (r Rect) Area() float64 {17	return r.Width * r.Height18}19 20type Circle struct {21	Radius float6422}23 24func (c Circle) Area() float64 {25	return math.Pi * c.Radius * c.Radius26}27 28func main() {29	var s Shape30	s = Rect{Width: 2, Height: 5}31	fmt.Println(s.Area())32	s = Circle{Radius: 2}33	fmt.Printf("%.2f\n", s.Area())34	fmt.Printf("%T\n", s)35}
Output
10
12.57
main.Circle

%T shows the type of what s holds right now: main.Circle (the Circle in package main).

Different shapes in one slice

A []Shape can hold rectangles and circles side by side. One loop adds up their areas:

prog.go
1package main2 3import (4	"fmt"5	"math"6)7 8type Shape interface {9	Area() float6410}11 12type Rect struct {13	Width, Height float6414}15 16func (r Rect) Area() float64 {17	return r.Width * r.Height18}19 20type Circle struct {21	Radius float6422}23 24func (c Circle) Area() float64 {25	return math.Pi * c.Radius * c.Radius26}27 28func main() {29	shapes := []Shape{30		Rect{Width: 3, Height: 4},31		Circle{Radius: 1},32		Rect{Width: 1, Height: 1},33	}34	total := 0.035	for _, s := range shapes {36		total += s.Area()37	}38	fmt.Printf("Total area: %.2f\n", total)39}
Output
Total area: 16.14

Add a new kind of shape later (a triangle), and as long as it has Area, this loop doesn't change at all.

A missing method

A type that lacks a method the interface asks for can't be used as that interface. The compiler names the missing method:

prog.go
1package main2 3import "fmt"4 5type Shape interface {6	Area() float647}8 9type Triangle struct {10	Base, Height float6411}12 13func main() {14	var s Shape = Triangle{Base: 3, Height: 4}15	fmt.Println(s.Area())16}
Output
# command-line-arguments
./prog.go:14:16: cannot use Triangle{…} (value of struct type Triangle) as Shape value in variable declaration: Triangle does not implement Shape (missing method Area)

Triangle does not implement Shape (missing method Area): Triangle has no Area method. Add func (t Triangle) Area() float64 and it compiles.

One more case: when a method has a pointer receiver, only the pointer *Counter has that method, not Counter itself:

prog.go
1package main2 3import "fmt"4 5type Counter struct {6	N int7}8 9func (c *Counter) Inc() {10	c.N++11}12 13type Incrementer interface {14	Inc()15}16 17func main() {18	var c Counter19	var i Incrementer = c20	i.Inc()21	fmt.Println(c.N)22}
Output
# command-line-arguments
./prog.go:19:22: cannot use c (variable of struct type Counter) as Incrementer value in variable declaration: Counter does not implement Incrementer (method Inc has pointer receiver)

method Inc has pointer receiver: what goes into the interface must be &c. Calling c.Inc() directly is fine, because Go takes the address of the variable for you (lesson 12); putting c into an interface doesn't do that, so you write &c yourself. That makes sense: if a copy of c went in, Inc would change the copy.

Guess first

What does this show?

1package main2 3import "fmt"4 5type Counter struct {6	N int7}8 9func (c *Counter) Inc() {10	c.N++11}12 13type Incrementer interface {14	Inc()15}16 17func main() {18	var c Counter19	var i Incrementer = &c20	i.Inc()21	i.Inc()22	fmt.Println(c.N)23}

fmt.Stringer

Why did the String method from the last lesson work? The fmt package has an interface:

type Stringer interface { String() string }

When fmt.Println gets a value, it first checks whether the value implements fmt.Stringer; if it does, it calls String. Your own functions can take a fmt.Stringer too:

prog.go
1package main2 3import "fmt"4 5type Employee struct {6	Name string7	Age  int8}9 10func (e Employee) String() string {11	return fmt.Sprintf("%s (age %d)", e.Name, e.Age)12}13 14func introduce(s fmt.Stringer) {15	fmt.Println("This is " + s.String())16}17 18func main() {19	e := Employee{Name: "Maya", Age: 28}20	fmt.Println(e)21	introduce(e)22}
Output
Maya (age 28)
This is Maya (age 28)

The empty interface: any

An interface that asks for no methods at all is interface{}, and Go gives it a short name, any. Every type implements it, so a variable of type any can hold anything:

prog.go
1package main2 3import "fmt"4 5func main() {6	var v any7	v = 428	fmt.Printf("%v %T\n", v, v)9	v = "hello"10	fmt.Printf("%v %T\n", v, v)11	things := []any{3, "pen", 4.5, true}12	fmt.Println(things)13}
Output
42 int
hello string
[3 pen 4.5 true]

The parameters of fmt.Println are any, which is why it can show anything. But once a value is inside an any, Go only knows "it's a value", so you can't add to it directly: first you have to get the value back out.

Type assertions

v.(int) is a type assertion: it says "v holds an int" and takes it out. Add an ok to ask first:

prog.go
1package main2 3import "fmt"4 5func main() {6	var v any = 427	n := v.(int)8	fmt.Println(n + 1)9	s, ok := v.(string)10	fmt.Println(ok, s == "")11}
Output
43
false true

v doesn't hold a string, so ok is false and s is the zero value "" (s == "" is true). Without ok, a wrong guess makes the program panic:

prog.go
1package main2 3import "fmt"4 5func main() {6	var v any = "42"7	n := v.(int)8	fmt.Println(n)9}
Output
panic: interface conversion: interface {} is string, not int

goroutine 1 [running]:
main.main(...)
	/tmp/sandbox/prog.go:7 +0x1d
exit status 2

What happened: The type assertion failed: the interface holds a different type.

interface conversion: interface {} is string, not int: v holds a string, not an int. (interface {} is any.) When you're not sure what's inside, use the form with ok.

Type switches

To handle each type differently, use a type switch. With switch x := v.(type), inside each case the variable x has that case's type:

prog.go
1package main2 3import "fmt"4 5func describe(v any) string {6	switch x := v.(type) {7	case int:8		return fmt.Sprintf("an int, plus one is %d", x+1)9	case string:10		return fmt.Sprintf("a string of %d bytes", len(x))11	case bool:12		return "a true/false value"13	default:14		return fmt.Sprintf("another type: %T", x)15	}16}17 18func main() {19	for _, v := range []any{7, "go", true, 2.5} {20		fmt.Println(describe(v))21	}22}
Output
an int, plus one is 8
a string of 2 bytes
a true/false value
another type: float64

Practice

Write the code, then press Check. A hint costs nothing; the answer is there when you need it.

Exercise 1

Make a square a shape too

This program doesn't compile: Square isn't a Shape yet. Give Square an Area method (side times side) so the program shows the area of each shape.

It should print
6
9
25
exercise1.go
1package main2 3import "fmt"4 5type Shape interface {6	Area() float647}8 9type Rect struct {10	Width, Height float6411}12 13func (r Rect) Area() float64 {14	return r.Width * r.Height15}16 17type Square struct {18	Side float6419}20 21func main() {22	shapes := []Shape{Rect{Width: 2, Height: 3}, Square{Side: 3}, Square{Side: 5}}23	for _, s := range shapes {24		fmt.Println(s.Area())25	}26}
Output
Press Run or Ctrl+Enter
Exercise 2

Which type is inside?

Finish kind: return number if v holds an int, text if it holds a string, and other for anything else.

It should print
number
text
other
number
exercise2.go
1package main2 3import "fmt"4 5func kind(v any) string {6	return "other"7}8 9func main() {10	for _, v := range []any{10, "apple", 3.5, -2} {11		fmt.Println(kind(v))12	}13}
Output
Press Run or Ctrl+Enter
Exercise 3

Add up only the ints

Finish sumInts: add up the ints in values, count how many are not ints, and return both. Use a type assertion with ok. The program tries two sets of values.

It should print
Sum of ints: 20 Others: 3
Sum of ints: 10 Others: 2
exercise3.go
1package main2 3import "fmt"4 5func sumInts(values []any) (int, int) {6	sum := 07	others := 08	return sum, others9}10 11func main() {12	sum, others := sumInts([]any{3, "pen", 4.5, 7, true, 10})13	fmt.Println("Sum of ints:", sum, "Others:", others)14	sum, others = sumInts([]any{"milk", 5, 5, 2.5})15	fmt.Println("Sum of ints:", sum, "Others:", others)16}
Output
Press Run or Ctrl+Enter

What you learned

  • An interface is a set of methods: type Shape interface { Area() float64 }.
  • A type with all the methods implements the interface automatically, with no implements; a []Shape can hold different types.
  • A missing method is a compile error, does not implement … (missing method …); for pointer-receiver methods, put &c in.
  • fmt.Println uses the fmt.Stringer interface: if there's a String() string, it calls it.
  • any holds anything; v.(int) takes the value out and panics on a wrong guess, while n, ok := v.(int) doesn't.
  • switch x := v.(type) handles each type in its own case.
Finished this lesson?Mark it. Progress is saved in this browser, and you can undo it.