Skip to content
Theory · Interfaces · lesson 1 of 10

Interfaces

What you'll learn

What you'll learn:

  • explain what an interface is and how a type implements one without the word implements;
  • change how fmt prints your type with a String method;
  • store a value of any type in any and get it back out with a type assertion;
  • pick an action by a value's type with a type switch.
A courier job ad titled Drive() with a student, a retiree and a taxi driver lined up below it

Picture a job ad for a courier. It does not say who is wanted: a student, a retiree or a former taxi driver. It says something else: "can drive a car and knows the city". Anyone who can do those two things fits. The employer does not care what the candidate did before—only what they can do.

Programs run into a similar situation. Different types can have methods with the same name, parameters and result. The rectangle and the square below both have an Area method that returns the shape's area:

GO
package main

import "fmt"

type Rect struct{ Width, Height float64 }

type Square struct{ Side float64 }

func (r Rect) Area() float64   { return r.Width * r.Height }
func (s Square) Area() float64 { return s.Side * s.Side }

func main() {
    r := Rect{Width: 2, Height: 3}
    fmt.Println(r.Area(), Square{Side: 2}.Area()) // 6 4
}

Where is the problem? A function that needs an area has to name the type of its parameter. The function func printArea(r Rect) accepts only a Rect, and a call with a Square does not compile. Writing a separate copy of the function for each shape is awkward: the copies have identical bodies, and only the parameter type differs. You need a way to say "any shape that can compute its area will do".

Declaring an interface

An interface is a type that lists methods: their names, parameters and results. These methods have no bodies. This is the job ad: a list of what a value must be able to do. The declaration type Shape interface { … } has these parts:

  • type Shape—a new type named Shape;
  • interface—the kind of type: a list of methods;
  • Area() float64 inside the braces—a required method: name, parameters and result, with no body.

The Shape interface requires one method, and the function printArea accepts any value of type Shape:

GO
type Shape interface {
    Area() float64
}

func printArea(s Shape) {
    fmt.Println(s.Area())
}

The parameter s of printArea has the type Shape. Inside the function, one thing is known about s: it has an Area method that can be called. Which type stands behind s, the function does not know, and it does not need to.

No implements keyword

A type implements an interface if it has all of the interface's methods with the same parameters and results. People also say the type satisfies the interface. You don't declare this: Go has no implements keyword.

Here the job-ad picture ends. In real life a candidate comes and applies, but in Go a type declares nothing and does not even know about Shape. The compiler matches the methods, and it is strict: an Area method that returns int instead of float64 does not fit.

The compiler checks the match in two places:

  • when a function with a parameter of an interface type is called;
  • when a value is assigned to a variable of an interface type.

If the type lacks a required method, or the method's result has a different type, the program does not compile.

Different types, one interface

Rect and Square both have an Area() float64 method, so both implement Shape. The function printArea accepts either of them, and a slice []Shape can hold them side by side:

GO
printArea(Rect{Width: 2, Height: 3}) // 6
printArea(Square{Side: 2})           // 4
shapes := []Shape{Square{Side: 1}, Rect{Width: 1, Height: 5}}
fmt.Println(len(shapes), shapes[1].Area()) // 2 5

Let's walk through the example line by line:

  1. The first call passes a rectangle to printArea and prints 6.
  2. The second passes a square and prints 4.
  3. The slice shapes holds two shapes of different types. Each element holds a value of its own concrete type.
  4. len(shapes) gives 2, and shapes[1].Area() runs the Area method of the type stored inside—here a Rect with area 5.

Pointer receivers and interfaces

Interviewers like to ask this: does Rect implement Shape if the method is declared as func (r *Rect) Area() float64? The answer depends on the kind of receiver.

A method with a pointer receiver

No, it does not. A method with a pointer receiver belongs to the type *Rect, and Rect does not have it. So var s Shape = Rect{Width: 2, Height: 3} does not compile: Rect does not implement Shape (method Area has pointer receiver). An address works: var s Shape = &Rect{Width: 2, Height: 3}.

Calling r.Area() on a variable r still works: Go takes its address for you, as you saw in the previous theme. But an interface needs a type that really has the method.

A method with a value receiver

The reverse rule is gentler: a method with a value receiver belongs to both Rect and *Rect. So with the methods from our example, both types implement Shape.

Common mistake

A struct field was accessed through a variable of an interface type:

GO
var s Shape = Rect{Width: 2, Height: 3}
fmt.Println(s.Width) // ✗
TEXT
s.Width undefined (type Shape has no field or method Width)

The program does not compile: an interface lists only methods, and it has no fields. Even though s holds a Rect with a Width field, only what Shape declares is available through s—the Area method. Through the job ad you see only what the ad says.

Key points

  • An interface lists methods; a type implements it if it has all of them with the same parameters and results.
  • Go has no implements keyword: the compiler checks the match.
  • A method with a *Rect receiver belongs only to *Rect, so *Rect implements the interface and Rect does not.
  • Through a variable of an interface type you reach only the interface's methods, not fields.

Subscribe to take the course

The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.

Sign in another way