Concept · Go

Implicit interfaces

A Go type satisfies an interface just by having the methods — so define small interfaces where you use them.

3 minduck typingaccept interfaces return structsio.Readerstructural typing

In Java or C#, a class says implements Reader. Go has no such keyword. A type satisfies an interface automatically if it has the interface's methods with matching signatures. The compiler checks it wherever the value is used as that interface.

go
type Reader interface {
	Read(p []byte) (n int, err error)
}

// *os.File, *bytes.Buffer, *gzip.Reader, net.Conn and http.Response.Body
// all satisfy io.Reader, and none of them mention it.
func countBytes(r io.Reader) (int64, error) {
	return io.Copy(io.Discard, r)
}

Consequences

Define interfaces where you use them, not where you implement them. The consumer decides what it needs:

go
// In the package that sends welcome emails, not in the user store package
type userGetter interface {
	GetUser(ctx context.Context, id int64) (User, error)
}

func SendWelcome(ctx context.Context, users userGetter, id int64) error { /* ... */ }

Your *PostgresStore with its 20 methods satisfies userGetter without knowing it exists. In tests, a fake with one method does too. No mocking framework is needed.

Keep interfaces small. The standard library's most useful interfaces have one method: io.Reader, io.Writer, fmt.Stringer, error and http.Handler. Small interfaces are easy to satisfy and easy to fake, and they compose: io.ReadWriter is just both.

Accept interfaces, return structs. Functions take the narrowest interface they need and return concrete types, so callers get every method without a type assertion.

Gotchas

  • Don't create an interface before you have two implementations, or a test that needs one. A UserService interface mirroring a single userService struct only adds indirection.
  • Pointer vs value receivers. If methods have pointer receivers (func (s *Store) Get…), only *Store satisfies the interface, not Store.
  • A nil pointer in an interface isn't a nil interface. Returning a typed (*MyErr)(nil) as an error gives a non-nil error. Return a literal nil. This matters most for errors, since error is itself an interface.
  • Assert satisfaction at compile time when it matters: var _ http.Handler = (*Server)(nil).

Middleware works the same way: anything with ServeHTTP is an http.Handler, so handlers wrap handlers.

Learn it in depth