Recursive interfaces

May 8, 2023

Check out this week’s episode of the Cup o’ Go podcast: What the ʕ◔ϖ◔ʔ? New merch, TDD book interview with Adelina Simion, and more


You can probably guess Go’s rules about recursively embedding interfaces. In short: It’s not allowed.

General interfaces

An interface type T may not embed a type element that is, contains, or embeds T, directly or indirectly.

// illegal: Bad may not embed itself
type Bad interface {
	Bad
}

// illegal: Bad1 may not embed itself using Bad2
type Bad1 interface {
	Bad2
}
type Bad2 interface {
	Bad1
}

// illegal: Bad3 may not embed a union containing Bad3
type Bad3 interface {
	~int | ~string | Bad3
}

// illegal: Bad4 may not embed an array containing Bad4 as element type
type Bad4 interface {
	[10]Bad4
}

Note however, that, an interface method may refer to the interface itself. This is perfectly valid:

type Fooer interface {
	Foo() Fooer
}

In fact, it’s actually quite common. You’ll see this pattern with chainable methods. An example can be found in the golang.org/x/exp/slog package’s Handler type:

type Handler interface {
	/* snip */

	// WithAttrs returns a new Handler whose attributes consist of
	// both the receiver's attributes and the arguments.
	// The Handler owns the slice: it may retain, modify or discard it.
	WithAttrs(attrs []Attr) Handler

Here we see that a Handler implements a WithAttrs method that in turn returns a Handler, meaning it is possible to chain these:

	var h slog.Handler
	h = h.WithAttrs(/* ... */).WithAttrs(/* ... */)

Quotes from The Go Programming Language Specification Version of December 15, 2022

Share this