Declaring Interfaces

An interface is a type that lists method names and nothing else, so one function can work with many concrete types.

An interface is a type that holds a list of method names. A method is a function attached to a type, written with a receiver in front of its name. The interface says what a value must be able to do. It says nothing about what the value is made of.

You declare an interface to describe what your code needs. A function that takes a Shape works with a rectangle, a circle, or a shape nobody has written yet. Inside that function, only the listed methods can be called.

What an interface variable holds

A variable of interface type stores two things. It stores the concrete type that was put into it, and the value of that type. Concrete means a real type with real data, such as a struct. The struct's fields are not reachable through the interface variable. Only the methods in the list are.

Longhand and shorthand

An interface can list its methods one by one. It can also embed another interface by name, which copies that interface's methods in. Both forms below declare the same two methods, and the two types are interchangeable.

type Closer interface {
	Close() error
}

// Longhand: both methods written out.
type FileA interface {
	Close() error
	Name() string
}

// Shorthand: Closer is embedded, which brings Close in.
type FileB interface {
	Closer
	Name() string
}

interface{} lists no methods at all, so every type satisfies it and the compiler checks nothing. Since Go 1.18 the name any is an alias for interface{}; the two spellings are the same type.

Example

Example · go
package main

import "fmt"

// Shape lists the methods a value must have. It holds no data.
type Shape interface {
	Area() float64
	Name() string
}

// Rect is a concrete type: a struct with real fields.
type Rect struct {
	W, H float64
}

func (r Rect) Area() float64 { return r.W * r.H }

func (r Rect) Name() string { return "rect" }

// describe can call only the two methods Shape lists.
func describe(s Shape) {
	fmt.Printf("%s has area %.1f\n", s.Name(), s.Area())
}

func main() {
	// A Rect value is stored in a variable of interface type.
	var s Shape = Rect{W: 3, H: 4}
	describe(s)
}

// Output:
// rect has area 12.0

When to use it

  • Write one report function that works with every kind of shape, record or job in a list.
  • Describe what a service needs from its database so a test can supply something simpler.
  • Give a package a single named type for "anything that can be closed" instead of repeating the method signature.

More examples

One slice, two concrete types

A slice of an interface type can hold values of different concrete types at the same time, because each element stores its own type alongside its data. Reach for this when you have a list of things that must all answer the same question, such as every shape on a drawing or every job in a queue.

Example · go
package main

import "fmt"

type Shape interface {
	Area() float64
}

type Rect struct {
	W, H float64
}

func (r Rect) Area() float64 { return r.W * r.H }

type Square struct {
	Side float64
}

func (s Square) Area() float64 { return s.Side * s.Side }

func total(shapes []Shape) float64 {
	sum := 0.0
	for _, s := range shapes {
		sum += s.Area()
	}
	return sum
}

func main() {
	// The slice holds a Rect and a Square side by side.
	shapes := []Shape{Rect{W: 2, H: 3}, Square{Side: 4}}
	fmt.Printf("total: %.1f\n", total(shapes))
}

// Output:
// total: 22.0

Building a bigger interface from smaller ones

Embedding keeps the small interfaces usable on their own while giving a name to the combination. Use it when some callers need the full set of methods and others need only one of them, which is exactly how the standard library builds io.ReadCloser out of io.Reader and io.Closer.

Example · go
package main

import "fmt"

// Two small interfaces. error is a built-in interface type; a nil
// error means nothing went wrong. Stage 3 covers it properly.
type Reader interface {
	Read(p []byte) (int, error)
}

type Closer interface {
	Close() error
}

// Embedding both is the same as writing Read and Close out by hand.
type ReadCloser interface {
	Reader
	Closer
}

// countBytes needs only Read, so it asks for the smaller interface.
// Any ReadCloser can still be passed to it.
func countBytes(r Reader) (int, error) {
	buf := make([]byte, 8)
	return r.Read(buf)
}

// memory satisfies both halves, so it satisfies ReadCloser.
type memory struct {
	data []byte
}

func (m *memory) Read(p []byte) (int, error) {
	n := copy(p, m.data)
	m.data = m.data[n:]
	return n, nil
}

func (m *memory) Close() error { return nil }

func main() {
	var rc ReadCloser = &memory{data: []byte("abcd")}
	n, err := countBytes(rc)
	if err != nil {
		fmt.Println("read failed:", err)
		return
	}
	fmt.Println("read", n, "bytes")
	if err := rc.Close(); err != nil {
		fmt.Println("close failed:", err)
	}
}

// Output:
// read 4 bytes

An interface as a struct field

Storing an interface in a struct field is how a program is wired together: the field says what the struct needs, and the value in it decides how that need is met. This is the shape nearly every Go service uses for its database, its cache and its outbound clients.

Example · go
package main

import "fmt"

// Store is what the service needs from its storage, and no more.
type Store interface {
	Get(key string) (string, bool)
}

// mapStore keeps everything in memory. A map type can have
// methods, so it satisfies Store.
type mapStore map[string]string

func (m mapStore) Get(key string) (string, bool) {
	v, ok := m[key]
	return v, ok
}

// Service holds the interface, not a concrete store, so the real
// store can be swapped for a simple one in a test.
type Service struct {
	store Store
}

func (s Service) Lookup(key string) string {
	v, ok := s.store.Get(key)
	if !ok {
		return "missing"
	}
	return v
}

func main() {
	svc := Service{store: mapStore{"go": "1.27"}}
	fmt.Println(svc.Lookup("go"))
	fmt.Println(svc.Lookup("rust"))
}

// Output:
// 1.27
// missing

Discussion

  • Be the first to comment on this lesson.