Implicit Satisfaction

A Go type satisfies an interface simply by having the right methods; there is no implements keyword and nothing to register.

In Go, a type satisfies an interface by having the methods the interface lists. There is no implements keyword and no list to sign up to. If an interface lists String() string, then every type with a method named String that takes nothing and returns a string satisfies it. The compiler checks this at the point where you use the value as that interface.

That has a useful consequence. The interface can be written long after the types that satisfy it, by somebody else, in a different package. The author of the type never has to know your interface exists.

Matching means matching exactly

The method name, the parameter types and the result types must all line up, in order. Parameter names are ignored, so Get(key string) and Get(k string) are the same method. An extra parameter, a missing result, or a different type is a different method, and the type does not satisfy the interface. The compile error names the method and says it is missing.

Asking the compiler to check now

Satisfaction is only checked where the value is used, which can be far away. A declaration with the blank identifier _ moves the check next to the type. Both lines below do the same job; the first allocates nothing.

var _ fmt.Stringer = (*Temp)(nil) // typed nil pointer
var _ fmt.Stringer = &Temp{}      // a real value, then discarded

If somebody renames the method later, the build fails on that line instead of somewhere unrelated.

Example

Example · go
package main

import "fmt"

// Celsius says nothing about any interface.
type Celsius float64

// fmt.Stringer lists exactly one method:
//	String() string
func (c Celsius) String() string {
	return fmt.Sprintf("%.1f C", float64(c))
}

// Compile-time check, placed next to the type. It stores nothing
// and fails the build if String is ever renamed or changed.
var _ fmt.Stringer = Celsius(0)

func main() {
	c := Celsius(21.5)

	// fmt looks for a String method at run time and uses it.
	fmt.Println(c)

	// The same value, used through the interface.
	var s fmt.Stringer = c
	fmt.Println(s.String())
}

// Output:
// 21.5 C
// 21.5 C

When to use it

  • Define the interface your package needs over types from another package you cannot change.
  • Add a String method so your type prints the way you want wherever fmt is used.
  • Catch a renamed method at build time with a blank-identifier assertion beside the type.

More examples

Satisfying an interface with a type you did not write

Because satisfaction is implicit, you can declare an interface that existing standard-library types already fit. This is how you write code against a type you do not own while keeping the option to pass in your own type later.

Example · go
package main

import (
	"fmt"
	"strings"
)

// Appender is declared here, today. strings.Builder was written
// years earlier and knows nothing about it.
type Appender interface {
	WriteString(s string) (int, error)
	String() string
}

func writeAll(a Appender, parts []string) error {
	for _, p := range parts {
		if _, err := a.WriteString(p); err != nil {
			return err
		}
	}
	return nil
}

func main() {
	var b strings.Builder

	// Both of Builder's matching methods have pointer receivers,
	// so &b satisfies Appender.
	if err := writeAll(&b, []string{"a", "b", "c"}); err != nil {
		fmt.Println("write failed:", err)
		return
	}
	fmt.Println(b.String())
}

// Output:
// abc

Value receiver or pointer receiver changes who satisfies

The methods of *T include those declared on T, but the methods of T do not include those declared on *T. Keep this example in mind the first time a struct "obviously" has the method and the compiler disagrees: the fix is almost always to pass a pointer.

Example · go
package main

import "fmt"

type Incrementer interface {
	Inc()
}

type Counter struct {
	n int
}

// Inc has to change the Counter, so its receiver is *Counter.
func (c *Counter) Inc() { c.n++ }

// *Counter has Inc, so this check passes.
var _ Incrementer = (*Counter)(nil)

// Counter does not. Uncommenting the next line fails the build,
// because Inc has a pointer receiver.
// var _ Incrementer = Counter{}

func main() {
	c := Counter{}

	// Taking the address gives a *Counter, which satisfies the
	// interface.
	var i Incrementer = &c
	i.Inc()
	i.Inc()
	fmt.Println(c.n)
}

// Output:
// 2

A near miss does not count

Satisfaction is decided on the whole signature, not the method name. When a type refuses to satisfy an interface, compare the two signatures character by character: an extra parameter or a missing second result is the usual cause.

Example · go
package main

import "fmt"

type Labeller interface {
	Label() string
}

type ID int

// Wrong shape: Label takes a parameter, so ID does not satisfy
// Labeller. The interface needs Label() string exactly.
func (i ID) Label(prefix string) string {
	return fmt.Sprintf("%s%d", prefix, int(i))
}

// SKU has the exact method, so it satisfies Labeller.
type SKU int

func (s SKU) Label() string { return fmt.Sprintf("sku-%d", int(s)) }

var _ Labeller = SKU(0)

func main() {
	// var l Labeller = ID(7) // build error: wrong signature
	var l Labeller = SKU(7)
	fmt.Println(l.Label())
	fmt.Println(ID(7).Label("id-"))
}

// Output:
// sku-7
// id-7

Discussion

  • Be the first to comment on this lesson.