Interfaces
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:
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}
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:
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}
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:
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}
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:
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}
# 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:
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}
# 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.
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}
i holds a pointer to c, so i.Inc() changes c. It's called twice, so the answer is 2.
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:
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}
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:
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}
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:
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}
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:
1package main2 3import "fmt"4 5func main() {6 var v any = "42"7 n := v.(int)8 fmt.Println(n)9}
panic: interface conversion: interface {} is string, not int
goroutine 1 [running]:
main.main(...)
/tmp/sandbox/prog.go:7 +0x1d
exit status 2What 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:
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}
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.
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.
6 9 25
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}
Which type is inside?
✓Finish kind: return number if v holds an int, text if it holds a string, and other for anything else.
number text other number
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}
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.
Sum of ints: 20 Others: 3 Sum of ints: 10 Others: 2
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}
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[]Shapecan hold different types. - A missing method is a compile error,
does not implement … (missing method …); for pointer-receiver methods, put&cin. fmt.Printlnuses thefmt.Stringerinterface: if there's aString() string, it calls it.anyholds anything;v.(int)takes the value out and panics on a wrong guess, whilen, ok := v.(int)doesn't.switch x := v.(type)handles each type in its own case.