Interfaces
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Go 1.27 (go1.27.2), checked on 2026-10-18; every go.mod shown has the line go 1.27.
An interface is a set of method signatures, and a type satisfies it just by having those methods: there is no
implements keyword, no declaration on the implementing side, and the implementing type does not even need to know
the interface exists. Interfaces are Go’s only mechanism for run-time polymorphism and, together with
structs and embedding, they replace the class hierarchies of
other languages. The standard library is built on a handful of tiny interfaces (io.Reader, error,
fmt.Stringer, http.Handler) that make unrelated packages fit together.
The page goes from the rules to the representation to the idioms: what an interface type is and how it is satisfied,
how an interface value is stored (and why a nil pointer inside an interface is not a nil interface), any, type
assertions and type switches, comparing interfaces, the standard interfaces every Go programmer meets, the
"accept interfaces, return structs" guideline, compile-time assertions, optional interfaces, and the link with
generic constraints. The previous page, Methods and Receivers,
explains method sets and receivers; the next ones are
Composition and Object-Oriented Design,
Generics and
Error Handling. For other languages' versions of the same idea see
the Rust traits and
trait objects pages (which share this page’s
shelf domain), Java interfaces,
C# interfaces,
Swift protocols and
TypeScript interfaces.
Every example was compiled, vetted with go vet and run with Go 1.27.2; the output shown is the real output (the
compiler and vet messages are likewise copied from real runs). The go.mod of the example module is:
module example.com/shelf
go 1.27
The examples use the shelf library catalogue: the catalog package declares Book, ISBN (a defined string type
with a String method), Loan, LoanState (an iota enumeration with a String method), the Store interface and
the in-memory *MemStore. Store is:
// Store persists books.
type Store interface {
Get(isbn ISBN) (Book, bool)
Put(b Book)
All() []Book
}
What an interface is
The specification (Interface types) says that an interface type defines a type set: the set of all types that implement the interface. A basic interface lists methods (and may embed other basic interfaces); the method set of the interface is exactly the list of its methods. A variable of interface type can hold any value whose type’s method set contains all of them, and calling a method through the variable calls the method of the value’s dynamic type.
The arrows in the diagram do not exist in the Go source: nothing in MemStore or fakeStore names Store.
Satisfaction is structural, decided by the compiler from the method sets. That has three consequences that shape
Go programs.
-
The interface can be declared after the type, and by someone else. A package that needs "something with a
Getmethod" declares that interface itself; the types it will later receive, written years earlier, already satisfy it. This is why the Go guideline is to define interfaces where they are used (see below), not where the implementation lives. -
There is no list of implementers to maintain, and no cyclic dependency between the package of the interface and the package of the type.
-
Satisfaction can happen by accident. A method called
Close() erroron an unrelated type satisfiesio.Closer. That is deliberate (the contract is the signature), but the semantics are not checked; document them in the interface’s comment. The FAQ entry How can I guarantee my type satisfies an interface? and the one on why type T does not satisfy anEqual(T)interface state the rules from the user’s point of view.
package main
import (
"fmt"
"example.com/shelf/catalog"
)
// Describer is satisfied by any type with a Describe method.
type Describer interface {
Describe() string
}
// Book and Loan never mention Describer: they simply have the method.
type Book struct{ Title string }
func (b Book) Describe() string { return "book " + b.Title }
type Loan struct {
Member string
State catalog.LoanState
}
func (l Loan) Describe() string { return fmt.Sprintf("loan to %s (%v)", l.Member, l.State) }
func main() {
items := []Describer{
Book{Title: "Dune"},
Loan{Member: "ada", State: catalog.OnLoan},
}
for _, it := range items {
fmt.Println(it.Describe())
}
}
book Dune
loan to ada (on loan)
The precise rule: implementing an interface
A type T implements an interface I if T is not an interface and is an element of the type set of I, or if T
is itself an interface and its type set is a subset of the type set of I. A value of type T implements I when
T does (spec: Implementing an interface). In practice this means:
-
every method of
Imust exist in the method set ofTwith an identical signature: same name, same parameter types, same result types, and the same variadic-ness. Parameter and result names do not matter, and neither does the receiver’s name; -
for an exported method name the package does not matter, but an unexported method name belongs to its package, so an interface with an unexported method can only be satisfied by types from the same package (this is how a package "seals" an interface);
-
an interface satisfies another interface when its method set is a superset, so a
ReadWriteClosercan be assigned to aReadervariable with no conversion.
When a type does not satisfy an interface the compiler says exactly why: a missing method, a wrong signature or a pointer receiver. The three messages are worth knowing by heart.
package main
import (
"fmt"
"example.com/shelf/catalog"
)
type readOnly struct{}
func (readOnly) Get(isbn catalog.ISBN) (catalog.Book, bool) { return catalog.Book{}, false }
func (readOnly) All() []catalog.Book { return nil }
func main() {
var s catalog.Store = readOnly{} // Put is missing
fmt.Println(s.All())
}
./main.go:15:24: cannot use readOnly{} (value of struct type readOnly) as catalog.Store value in variable declaration: readOnly does not implement catalog.Store (missing method Put)
package main
import (
"fmt"
"io"
)
type shouty struct{}
func (shouty) Read(p []byte) int { return 0 } // wrong signature: no error result
func main() {
var r io.Reader = shouty{}
fmt.Println(r)
}
./main.go:13:20: cannot use shouty{} (value of struct type shouty) as io.Reader value in variable declaration: shouty does not implement io.Reader (wrong type for method Read)
have Read([]byte) int
want Read([]byte) (int, error)
The have/want lines in the second message are the quickest way to spot the usual mistake: a method with the right
name but a different signature (here the missing error result of Read).
Method sets decide who satisfies what
Satisfaction is decided by method sets (spec: Method sets):
| Type of the value | Method set contains | Satisfies an interface that needs |
|---|---|---|
|
methods declared with a value receiver |
only value-receiver methods |
|
methods with a value receiver and with a pointer receiver |
any method of |
|
the same as |
pointer-receiver methods are not available |
an embedded field |
promoted methods of |
value methods of |
The rule is deliberate: a pointer-receiver method needs an address to modify, and the copy inside an interface value has none that the caller could observe. The compiler therefore refuses the assignment, and the fix is to store a pointer.
package main
import "fmt"
type counter struct{ n int }
func (c *counter) Incr() { c.n++ }
type incrementer interface{ Incr() }
func main() {
var i incrementer = counter{} // counter has no Incr: only *counter does
i.Incr()
fmt.Println(i)
}
./main.go:12:22: cannot use counter{} (value of struct type counter) as incrementer value in variable declaration: counter does not implement incrementer (method Incr has pointer receiver)
The *MemStore in the shelf catalogue is the usual case: its methods lock a mutex, so they have pointer receivers,
and only *MemStore (never MemStore) satisfies Store. The full table of method sets, the automatic & and * on
direct calls and the reasoning for choosing a receiver are in
Methods and Receivers; the way embedding changes the method
set of the outer type is in Structs and Embedding.
Methods are not restricted to structs: any defined type declared in the same package can have them, so slices, maps and function types satisfy interfaces too.
package main
import (
"fmt"
"slices"
"sort"
"example.com/shelf/catalog"
)
// LoanStates is a slice type with methods: any named type may implement interfaces.
type LoanStates []catalog.LoanState
func (l LoanStates) String() string { return fmt.Sprintf("%d states", len(l)) }
// Counter is a map type with a method.
type Counter map[string]int
func (c Counter) Add(k string) { c[k]++ }
type adder interface{ Add(string) }
func main() {
var s fmt.Stringer = LoanStates{catalog.OnLoan}
fmt.Println(s)
c := Counter{}
var a adder = c // map is a reference type: value receiver can mutate
a.Add("x")
a.Add("x")
fmt.Println(c)
fmt.Println(sort.IsSorted(sort.IntSlice([]int{1, 2, 3})), slices.IsSorted([]int{3, 1}))
}
1 states
map[x:2]
true false
Source: A Tour of Go: Methods continued
Small interfaces and interface embedding
Go interfaces are small on purpose. The proverb "the bigger the interface, the weaker the abstraction" comes from
Rob Pike’s Go Proverbs, and the standard library follows it: io.Reader, io.Writer, io.Closer, fmt.Stringer,
error and sort.Interface have one to three methods each. A small interface is satisfied by many types, easy to
fake in a test and cheap to document. Effective Go (Interfaces and other
types) describes interfaces with one or two methods as the common case.
Larger interfaces are composed by embedding smaller ones (spec: Embedded interfaces). The method set of the new interface is the union of the embedded method sets plus any methods declared next to them.
package main
import (
"fmt"
"strings"
)
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
// ReadWriter embeds two interfaces: its method set is the union.
type ReadWriter interface {
Reader
Writer
}
// ReadWriteCloser embeds ReadWriter and Closer.
type ReadWriteCloser interface {
ReadWriter
Closer
}
// Since Go 1.14 embedded interfaces may overlap on identical methods.
type Named interface{ Name() string }
type Titled interface{ Name() string }
type Both interface {
Named
Titled
}
type book struct{ title string }
func (b book) Name() string { return strings.ToUpper(b.title) }
type buf struct{ data []byte }
func (b *buf) Read(p []byte) (int, error) { return copy(p, b.data), nil }
func (b *buf) Write(p []byte) (int, error) { b.data = append(b.data, p...); return len(p), nil }
func (b *buf) Close() error { return nil }
func main() {
var rwc ReadWriteCloser = &buf{}
var rw ReadWriter = rwc // a ReadWriteCloser is assignable to its sub-interface
fmt.Fprintf(rw, "hello")
fmt.Println(string(rwc.(*buf).data))
var both Both = book{"dune"}
fmt.Println(both.Name())
}
hello
DUNE
-
The union of method sets is a union by name: two embedded interfaces may declare the same method as long as the signatures are identical. Before Go 1.14 that was a compile error (
duplicate method), which is why older code and older books (Vivien, chapter 8) avoid it. Since Go 1.14 overlapping embedded interfaces are allowed, asBothshows (Go 1.14 release notes). -
An interface can embed any interface type, including ones from other packages (
io.ReadWriterembedsio.Readerandio.Writer), and since Go 1.18 can also embed type-set elements, which turns it into a constraint (see Interfaces as type sets). -
Embedding an interface in a struct is a different feature: the struct gets the interface’s methods as promoted methods, forwarded to whatever value the field holds. That is how decorators are written (below).
The standard library composes io this way: io.ReadWriter, io.ReadCloser, io.WriteCloser,
io.ReadWriteCloser, io.ReadSeeker, io.ReadWriteSeeker, io.ReadSeekCloser and io.WriteSeeker are all built from
Reader, Writer, Closer and Seeker, and io.ByteReader, io.RuneReader, io.WriterTo, io.ReaderFrom,
io.ReaderAt and io.WriterAt are further one- or two-method interfaces (io).
Interface values: a type and a value
A variable of interface type is not a reference to the object; it is a small value of its own that holds two things:
the dynamic type of what was stored and the dynamic value. The spec describes them as the "dynamic type" and
"dynamic value" of the interface value, and the Go blog’s
The Laws of Reflection explains the pair; the Tour shows it with %v and %T
(Interface values).
The implementation, which the spec does not require but every current gc toolchain follows, is a pair of machine words:
-
a non-empty interface such as
Storeorerrorstores a pointer to an itab (interface table): a runtime structure, built once per (interface type, concrete type) pair and cached, which holds the two types and an array of function pointers, one for each method of the interface, in alphabetical order; -
the empty interface
anyhas no methods to look up, so it stores the pointer to the concrete type’s descriptor directly; -
the second word points at the stored value: a pointer type is stored directly in the word, other values are copied to an allocation (or to read-only data for constants), which is why an interface holds a copy of a non-pointer value.
A method call through the interface (s.Get(i)) loads the function pointer from the itab and calls it with the data
word as the receiver: one indirect call, which is why the compiler cannot inline it unless it can prove the dynamic
type (devirtualization, which profile-guided optimization extends; see
Performance and PGO).
package main
import (
"fmt"
"unsafe"
)
type shape interface{ Area() float64 }
type sq struct{ s float64 }
func (q sq) Area() float64 { return q.s * q.s }
func main() {
var e any
var s shape
fmt.Println("sizeof any:", unsafe.Sizeof(e), "sizeof shape:", unsafe.Sizeof(s))
fmt.Println("sizeof func value:", unsafe.Sizeof(func() {}))
s = sq{3}
fmt.Println(s.Area())
}
sizeof any: 16 sizeof shape: 16
sizeof func value: 8
9
Source: unsafe: Sizeof
Both any and shape are 16 bytes on a 64-bit platform, whatever they hold; a plain function value is a single word.
unsafe.Sizeof reports 8 for the function here because a func value is a pointer to a closure object. These sizes are
implementation facts, not guarantees.
What assigning to an interface costs
Storing a value in an interface may allocate, because the data word must point at the value. A pointer needs nothing,
a small integer value and a constant often reuse static data, and a general struct is copied into a new allocation if
the interface escapes (is stored somewhere that outlives the call, such as a package variable). testing.AllocsPerRun
counts the allocations:
package main
import (
"fmt"
"testing"
)
type shape interface{ Area() float64 }
type rect struct{ w, h float64 }
func (r rect) Area() float64 { return r.w * r.h }
type big struct{ a, b, c, d int }
func (b big) Area() float64 { return float64(b.a) }
var sink shape
func main() {
r := rect{2, 3}
n := 1000 // not a constant, so the compiler cannot place the value in read-only data
fmt.Println("direct call allocs:", testing.AllocsPerRun(100, func() { _ = r.Area() }))
fmt.Println("boxing a struct allocs:", testing.AllocsPerRun(100, func() { n++; sink = big{n, 2, 3, 4} }))
fmt.Println("boxing a pointer allocs:", testing.AllocsPerRun(100, func() { sink = &r }))
fmt.Println("boxing a rect that escapes allocs:", testing.AllocsPerRun(100, func() { n++; sink = rect{float64(n), 1} }))
}
direct call allocs: 0
boxing a struct allocs: 1
boxing a pointer allocs: 0
boxing a rect that escapes allocs: 1
Source: testing: AllocsPerRun
The lesson is not "avoid interfaces": a one-allocation conversion on a path that runs a few thousand times a second is
nothing. It is to know that boxing exists when a profile shows allocation in a hot loop, and that APIs taking any
(fmt.Println, slog attributes, sync.Map) box their arguments. The measurement tools are in
Benchmarks, Fuzzing and synctest and the escape
analysis in Memory Management and Garbage Collection.
The nil interface and the nil pointer in an interface
An interface value is nil only when both words are nil: no dynamic type and no value. Assigning a nil pointer
of a concrete type to an interface produces a non-nil interface (the type word is set) that holds a nil pointer. This
is the most famous gotcha in the language: the FAQ gives it its own entry,
Why is my nil error value not equal to nil?.
package main
import (
"fmt"
"example.com/shelf/catalog"
)
func main() {
var s fmt.Stringer // nil interface: no type, no value
fmt.Printf("%T %v %t\n", s, s, s == nil)
s = catalog.OnLoan // dynamic type catalog.LoanState, value 1
fmt.Printf("%T %v %t\n", s, s, s == nil)
isbn, _ := catalog.ParseISBN("978-0-306-40615-7")
s = isbn // dynamic type catalog.ISBN, value is a string
fmt.Printf("%T %v\n", s, s)
var p *catalog.MemStore // nil pointer of a concrete type
var st catalog.Store = p // interface now holds (*catalog.MemStore, nil)
fmt.Printf("%T %t\n", st, st == nil)
}
<nil> <nil> true
catalog.LoanState on loan false
catalog.ISBN 9780306406157
*catalog.MemStore false
The same thing happens with the most common interface in Go, error:
package main
import (
"errors"
"fmt"
)
type ParseError struct{ Line int }
func (e *ParseError) Error() string { return fmt.Sprintf("parse error on line %d", e.Line) }
// bad returns a typed nil pointer wrapped in a non-nil error interface.
func bad(fail bool) error {
var perr *ParseError
if fail {
perr = &ParseError{Line: 3}
}
return perr
}
// good returns the nil interface explicitly.
func good(fail bool) error {
if fail {
return &ParseError{Line: 3}
}
return nil
}
func main() {
err := bad(false)
fmt.Println("bad(false) == nil:", err == nil)
fmt.Printf("dynamic type %T, value %v\n", err, err == (*ParseError)(nil))
err = good(false)
fmt.Println("good(false) == nil:", err == nil)
var pe *ParseError
fmt.Println(errors.As(bad(true), &pe), pe.Line)
}
bad(false) == nil: false
dynamic type *main.ParseError, value true
good(false) == nil: true
true 3
bad returns an error interface holding (*ParseError)(nil); the check err != nil in the caller is true, the
caller tries to handle an error that does not exist, and, if the handler calls err.Error(), the method runs with a
nil receiver and may panic. The rules to stay out of trouble are short:
-
a function that returns
errormust return the literalnilfor success, never a variable of a concrete pointer type. Declarevar err error, notvar err *ParseError, in functions that build an error incrementally; -
a method can be called on a nil pointer if its body handles it (Tour, methods/12), so a typed nil in an interface is not by itself a crash; it is an interface that looks like "there is a value" while the value is empty;
-
nilnessand other analyzers find some of these patterns (see vet and the compiler), but not all of them, so review function results that are declared as a concrete pointer type and returned as an interface.
Bodner (chapter 7) and Vivien (chapter 8) both explain the pair; the Bodner chapter on errors (chapter 8) repeats the
caveat for custom error types. The error-specific side (wrapping, errors.Is, errors.As) is in
Error Handling.
Calling a method on a nil interface
If the interface itself is nil (both words nil), calling a method has nothing to dispatch to and panics with a run-time
error; there is no NullPointerException-style message naming the method.
package main
import (
"fmt"
"example.com/shelf/catalog"
)
type speaker interface{ Speak() string }
type cat struct{ name string }
func (c *cat) Speak() string { return c.name + " says meow" }
type dog struct{ name string }
func (d dog) Speak() string { return d.name + " says woof" }
func main() {
defer func() { fmt.Println("recovered:", recover()) }()
// a method value binds its receiver when it is evaluated:
// for a pointer it binds the pointer, for a value it binds a copy
c := &cat{name: "tom"}
d := dog{name: "rex"}
var s1, s2 speaker = c, d
f1, f2 := s1.Speak, s2.Speak
c.name, d.name = "felix", "fido"
fmt.Println(f1())
fmt.Println(f2())
// method expression on an interface type: the receiver becomes the first argument
g := speaker.Speak
fmt.Println(g(d))
// calling a method on a nil interface panics
var st catalog.Store
_ = st.All()
}
felix says meow
rex says woof
fido says woof
recovered: runtime error: invalid memory address or nil pointer dereference
The program also shows the two flavours of method value: s1.Speak bound the pointer (so it saw the rename), while
s2.Speak bound a copy of the dog made when the method value was evaluated (so it did not), and the method
expression speaker.Speak turns the method into a function whose first parameter is the receiver. The related spec
sections are Method values and
Method expressions.
any and the empty interface
An interface with no methods is satisfied by every type. Since Go 1.18 it has the predeclared name any, defined as
an alias for interface{} (Predeclared identifiers,
Go 1.18 release notes); new code writes any, and the go fix modernizers (Go 1.26)
rewrite the old spelling. The two predeclared interface types besides any are error and comparable.
A value of type any can hold anything, and it tells the compiler nothing about it, so it must be inspected at run
time with a type assertion or a type switch before use. The old advice stays right: "`any` says nothing". Reach for
it only where the data is dynamically typed by nature: decoded JSON of unknown shape, the arguments of fmt.Printf,
the values of a heterogeneous container, a plugin boundary. Everywhere else a small interface or a type parameter
documents the requirement in the signature.
A frequent beginner mistake is to think that []T converts to []any implicitly. It does not: the two slices have
different element sizes and layouts, and there is no implicit conversion (FAQ,
Can I convert a [T to an []interface{}?]).
package main
import "fmt"
func main() {
titles := []string{"Dune", "Ubik"}
fmt.Println(titles...) // []string is not []any
}
./main.go:7:14: cannot use titles (variable of type []string) as []any value in argument to fmt.Println
Source: Go FAQ: Can I convert a [T to an []interface{}?]
Convert element by element, then spread with ...:
package main
import (
"fmt"
"strings"
)
func main() {
titles := []string{"Dune", "Ubik", "Solaris"}
// []string is not []any, even though string is assignable to any:
// the two slices have different memory layouts, so convert element by element.
args := make([]any, len(titles))
for i, t := range titles {
args[i] = strings.ToUpper(t)
}
fmt.Println(args...)
fmt.Println(len(args), cap(args))
}
DUNE UBIK SOLARIS
3 3
Type assertions
A type assertion x.(T) asks what is inside an interface value x
(Type assertions). It has two forms.
| Form | When T is a concrete type |
When T is an interface type |
|---|---|---|
|
asserts that the dynamic type is identical to |
asserts that the dynamic type implements |
|
|
same, with |
Rules to remember:
-
xmust have interface type. Asserting on a non-interface value is a compile error; convert toanyfirst if you really need to. -
The single-value form panics with a
*runtime.TypeAssertionErrorwhose message names both types; the panic is recoverable but should be treated as a programming error. -
The comma-ok form is the one to use whenever the type is not guaranteed.
-
If
Tis an interface, the assertion checks the method set at run time, which is how optional interfaces work. -
The compiler rejects an assertion that can never succeed:
package main
import (
"fmt"
"io"
)
type book struct{ title string }
func main() {
var r io.Reader
b, ok := r.(book) // book has no Read method: this can never succeed
fmt.Println(b, ok)
switch r.(type) {
case book:
}
}
./main.go:12:11: impossible type assertion: r.(book)
book does not implement io.Reader (missing method Read)
./main.go:16:7: impossible type switch case: book
r (variable of interface type io.Reader) cannot have dynamic type book (missing method Read)
package main
import (
"fmt"
"example.com/shelf/catalog"
)
func main() {
var v any = catalog.OnLoan
// comma-ok form: no panic, ok reports success
if s, ok := v.(catalog.LoanState); ok {
fmt.Println("LoanState", int(s), s)
}
n, ok := v.(int)
fmt.Println("int?", n, ok) // zero value and false
// assertion to an interface type checks the method set at run time
if st, ok := v.(fmt.Stringer); ok {
fmt.Println("Stringer:", st.String())
}
// single-value form panics when the assertion fails
defer func() {
r := recover()
fmt.Println("recovered:", r)
if e, ok := r.(error); ok {
fmt.Printf("%T\n", e)
}
}()
_ = v.(string)
}
LoanState 1 on loan
int? 0 false
Stringer: on loan
recovered: interface conversion: interface {} is catalog.LoanState, not string
*runtime.TypeAssertionError
The assertion v.(string) panics with interface conversion: interface {} is catalog.LoanState, not string; the
message still spells the empty interface the old way. A type-set interface (see below) cannot be the target type of an
assertion, only basic interfaces can.
Type switches
A type switch compares the dynamic type of an interface value with several types in one statement (spec:
Type switches). It is written like a switch whose tag is x.(type), with an
optional short variable declaration switch v := x.(type).
-
each
caselists one or more types (ornil); the first case that matches is chosen, and there is no fall-through; -
in a single-type case the variable
vhas that type; in a multi-type case, ordefault, it keeps the type ofx; -
a
case nilmatches only a nil interface (not an interface holding a nil pointer); -
a case with an interface type matches any dynamic type that implements it, so order cases from most specific to least specific (
errorbeforefmt.Stringerif a type could implement both); -
the compiler rejects impossible and duplicate cases, and an unused
vin every clause is the same "declared and not used" error as any other variable.
package main
import (
"fmt"
"time"
"example.com/shelf/catalog"
)
func describe(v any) string {
switch x := v.(type) {
case nil:
return "nil interface"
case int, int64: // several types: x keeps the interface type any
return fmt.Sprintf("integer %v (%T)", x, x)
case string:
return "string of length " + fmt.Sprint(len(x))
case catalog.ISBN: // concrete defined type
return "isbn " + string(x)
case error: // interface case, matched by method set
return "error: " + x.Error()
case fmt.Stringer:
return "stringer: " + x.String()
case []catalog.Book:
return fmt.Sprintf("%d books", len(x))
case func() time.Time:
return "a clock function"
default:
return fmt.Sprintf("something else: %T", x)
}
}
func main() {
var e error
for _, v := range []any{
nil, 42, int64(7), "dune", catalog.ISBN("9780306406157"),
fmt.Errorf("boom"), catalog.Overdue, []catalog.Book{{}, {}},
time.Now, 3.14, e,
} {
fmt.Println(describe(v))
}
}
nil interface
integer 42 (int)
integer 7 (int64)
string of length 4
isbn 9780306406157
error: boom
stringer: overdue
2 books
a clock function
something else: float64
nil interface
The last line is the nil interface e; the variable is a nil error, so case nil catches it. Duplicate and impossible
cases are compile errors:
package main
import "fmt"
func kind(v any) string {
switch v.(type) {
case int:
return "int"
case string, int:
return "dup"
}
return ""
}
func main() { fmt.Println(kind(1)) }
./main.go:9:15: duplicate case int in type switch
./main.go:7:7: previous case
When to use assertions and switches, and when not to
Type switches are idiomatic at the edges of a program, where data genuinely arrives as any: decoding
(encoding/json into any yields map[string]any, []any, float64, string, bool or nil), formatting,
parsing syntax trees, handling the several error types a call may return. In the middle of a program they are
usually a sign that an interface method is missing: a switch on the type of an argument is "polymorphism by hand",
and it must be updated every time a type is added, while an interface method is found by the compiler. The guideline
is to prefer a method, then a small closed set of types in a switch, and a type parameter when the operation is
the same for every type.
The standard interfaces
A Go programmer learns a few dozen standard interfaces early, because they are the "plugs" of the ecosystem: a type that
implements io.Reader can be given to any function that reads, and so on.
| Interface | Method(s) | Typical implementers | Consumed by |
|---|---|---|---|
|
|
|
|
|
|
|
every function returning a failure; |
|
|
|
|
|
|
|
|
|
|
files, connections, bodies |
|
|
|
your sortable slice types |
|
|
|
your handlers, |
|
|
|
|
|
|
|
types with a custom JSON shape |
|
|
|
custom column types |
|
|
|
the contexts of the |
every cancellable API |
|
|
types with custom |
|
|
see the packages |
custom heaps, flag types, file systems, log backends |
the respective packages |
Source: fmt: Stringer, io: Reader and net/http: Handler
Notes on the table:
-
Stringeris not built in (a common misreading of older books; Vivien chapter 1): it isfmt.Stringer, and the only predeclared interface types areany,errorandcomparable. -
A method named
String,Error,Read,Write,ReadByte,Format,Scan,MarshalJSONand so on is a commitment: thestdmethodscheck ofgo vetreports a method with a well-known name and the wrong signature. -
sort.Interfaceis the oldest of the group and the heaviest (three methods per type). Since Go 1.21slices.Sort,slices.SortFuncandslices.SortStableFuncsort any slice with a comparison function and no boilerplate; new code uses them, andsort.Interfaceremains useful for data structures that are not slices, forcontainer/heap, and forsort.Reverse. Theslicesandcmppackages are covered in Collections and Utility Packages.
package main
import (
"fmt"
"slices"
"sort"
"strings"
"example.com/shelf/catalog"
)
// byTitle implements sort.Interface (the classic three methods).
type byTitle []catalog.Book
func (s byTitle) Len() int { return len(s) }
func (s byTitle) Less(i, j int) bool { return s[i].Title < s[j].Title }
func (s byTitle) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func titles(bs []catalog.Book) []string {
var out []string
for _, b := range bs {
out = append(out, b.Title)
}
return out
}
func main() {
books := []catalog.Book{{Title: "Neuromancer"}, {Title: "Dune"}, {Title: "Ubik"}}
sort.Sort(byTitle(books))
fmt.Println(titles(books))
sort.Sort(sort.Reverse(byTitle(books)))
fmt.Println(titles(books))
// Since Go 1.21 a func and generics replace the interface in new code.
slices.SortFunc(books, func(a, b catalog.Book) int {
return strings.Compare(a.Title, b.Title)
})
fmt.Println(titles(books))
}
[Dune Neuromancer Ubik]
[Ubik Neuromancer Dune]
[Dune Neuromancer Ubik]
Source: sort: Interface
fmt.Stringer and error
String() and Error() are the two methods most types get. When fmt formats a value with %v or %s it asks, in
order, whether the value implements fmt.Formatter, then (for %#v) fmt.GoStringer, then (for %v, %s, %q, %x,
%X and others) error, then fmt.Stringer (fmt: Printing). A common bug is a
String method that calls fmt.Sprintf("%v", x) on its own receiver, which recurses forever; convert to the
underlying type first (string(i), int(s)). The shelf LoanState does that.
package main
import (
"errors"
"fmt"
"os"
"example.com/shelf/catalog"
)
// LoanError carries structured context; *LoanError implements error.
type LoanError struct {
ISBN catalog.ISBN
Reason string
}
func (e *LoanError) Error() string { return fmt.Sprintf("loan %s: %s", e.ISBN, e.Reason) }
func lend(isbn catalog.ISBN) error {
return &LoanError{ISBN: isbn, Reason: "already on loan"}
}
func main() {
isbn, _ := catalog.ParseISBN("978-0-306-40615-7")
// fmt.Stringer: Println, %v and %s call String
fmt.Println(isbn, catalog.OnLoan)
fmt.Printf("%v %s %d %q\n", catalog.Overdue, catalog.Overdue, catalog.Overdue, catalog.Overdue)
// error: the one-method interface for failures
err := lend(isbn)
fmt.Println(err)
var le *LoanError
if errors.As(err, &le) {
fmt.Println("reason:", le.Reason)
}
fmt.Fprintln(os.Stdout, "done")
}
9780306406157 on loan
overdue overdue 2 "overdue"
loan 9780306406157: already on loan
reason: already on loan
done
Source: fmt: Stringer
*LoanError has a pointer receiver, so lend returns &LoanError{...}; callers that want the fields use
errors.As (see below and Error Handling). The
stringer tool from golang.org/x/tools generates String methods for iota enumerations
(Variables, Constants and iota).
io.Reader, io.Writer and io.Closer
io.Reader and io.Writer are the most reused interfaces in the standard library, because they describe a stream
of bytes without saying where it comes from. Read fills the slice it is given and returns the number of bytes and an
error; a reader may return n > 0 and an error (including io.EOF) in the same call, so callers handle the bytes
before the error. io.Copy, io.ReadAll, io.MultiWriter, io.TeeReader, io.LimitReader, io.Pipe and bufio
compose them. The File page (I/O, Files and the Filesystem)
covers them in depth; here is the smallest pipeline:
package main
import (
"bytes"
"fmt"
"io"
"os"
"strings"
)
// upper wraps a Reader and upper-cases what it reads.
type upper struct{ r io.Reader }
func (u upper) Read(p []byte) (int, error) {
n, err := u.r.Read(p)
copy(p, bytes.ToUpper(p[:n]))
return n, err
}
// countWriter counts the bytes written through it.
type countWriter struct {
w io.Writer
n int
}
func (c *countWriter) Write(p []byte) (int, error) {
n, err := c.w.Write(p)
c.n += n
return n, err
}
func main() {
src := strings.NewReader("the left hand of darkness\n")
cw := &countWriter{w: os.Stdout}
n, err := io.Copy(cw, upper{src}) // Reader -> Writer pipeline
fmt.Println(n, err, cw.n)
// io.MultiWriter fans out to several Writers
var a, b bytes.Buffer
mw := io.MultiWriter(&a, &b)
fmt.Fprint(mw, "dune")
fmt.Println(a.String(), b.String())
// io.ReadAll and the interface compositions
var rc io.ReadCloser = io.NopCloser(strings.NewReader("abc"))
data, _ := io.ReadAll(rc)
fmt.Println(string(data), rc.Close())
}
THE LEFT HAND OF DARKNESS
26 <nil> 26
dune dune
abc <nil>
Source: io: package overview
The upper type and countWriter implement the interfaces with a few lines each, wrapping another Reader or
Writer; the wrappers can be stacked (upper{bufio.NewReader(...)}) because each knows only the interface of the
one inside. This "wrapper that takes and satisfies the same interface" is the Go form of the decorator pattern.
http.Handler and the function-type adapter
http.Handler has one method, so a struct with a ServeHTTP method is a handler. Writing a type just to hold one
function would be noise, so net/http declares type HandlerFunc func(ResponseWriter, *Request) with a method that
calls the function; converting a plain function to HandlerFunc produces a Handler. That is the adapter idiom:
a function type with a method that satisfies an interface.
package main
import (
"fmt"
"net/http"
"net/http/httptest"
)
// A struct can implement http.Handler...
type health struct{ version string }
func (h health) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "ok %s", h.version)
}
// ...and a function type can too: this is the adapter pattern.
// http.HandlerFunc is declared exactly like this in net/http.
type handlerFunc func(http.ResponseWriter, *http.Request)
func (f handlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) { f(w, r) }
func main() {
mux := http.NewServeMux()
mux.Handle("GET /health", health{version: "1.0"})
mux.Handle("GET /hello/{name}", handlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello %s", r.PathValue("name"))
}))
mux.HandleFunc("GET /bye", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "bye")
})
for _, path := range []string{"/health", "/hello/ada", "/bye", "/missing"} {
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, httptest.NewRequest("GET", path, nil))
fmt.Println(path, rec.Code, rec.Body.String())
}
}
/health 200 ok 1.0
/hello/ada 200 hello ada
/bye 200 bye
/missing 404 404 page not found
Source: net/http: HandlerFunc
mux.HandleFunc is the same conversion done for you. The patterns with a method and {name} wildcards, r.PathValue
and the 404 message come from the Go 1.22 ServeMux; the server side is in
net/http Clients and Servers. Bodner (chapter 7) and the
Go blog use HandlerFunc as the standard example of "functions can have methods".
encoding.TextMarshaler
encoding.TextMarshaler and TextUnmarshaler let a type decide how it appears as text. encoding/json uses them for
values and map keys that have no other JSON encoding, flag.TextVar uses them for command-line flags, and
encoding/xml and database/sql have similar hooks. The compile-time assertions after the type are discussed
below.
package main
import (
"encoding"
"encoding/json"
"fmt"
"example.com/shelf/catalog"
)
// Code is a two-letter language code that marshals as text.
type Code string
func (c Code) MarshalText() ([]byte, error) { return []byte("lang:" + string(c)), nil }
func (c *Code) UnmarshalText(b []byte) error {
if len(b) < 5 || string(b[:5]) != "lang:" {
return fmt.Errorf("bad code %q", b)
}
*c = Code(b[5:])
return nil
}
var (
_ encoding.TextMarshaler = Code("")
_ encoding.TextUnmarshaler = (*Code)(nil)
)
type Edition struct {
ISBN catalog.ISBN `json:"isbn"`
Lang Code `json:"lang"`
}
func main() {
b, err := json.Marshal(Edition{ISBN: "9780306406157", Lang: "en"})
fmt.Println(string(b), err)
// TextMarshaler also drives map keys
m := map[Code]int{"en": 3, "es": 1}
b, _ = json.Marshal(m)
fmt.Println(string(b))
var e Edition
err = json.Unmarshal([]byte(`{"isbn":"1","lang":"lang:fr"}`), &e)
fmt.Println(e, err)
err = json.Unmarshal([]byte(`{"lang":"fr"}`), &e)
fmt.Println(err)
}
{"isbn":"9780306406157","lang":"lang:en"} <nil>
{"en":3,"es":1}
{1 fr} <nil>
bad code "fr"
Source: encoding: TextMarshaler
Marshalers use a value receiver (so both Code and *Code satisfy TextMarshaler), unmarshalers a pointer
receiver (they must modify the value). That split, a value-receiver method on the write side and a pointer-receiver
one on the read side, is the normal pattern; see
encoding/json for the full marshaling rules.
Comparing interface values
Interface values are comparable with == and != when the dynamic types are comparable: two interface values are
equal if they have identical dynamic types and equal dynamic values, or both are nil
(Comparison operators). Consequently:
-
any(1) == any(int64(1))is false: different dynamic types; -
comparing an interface value with a non-interface value compares the dynamic type and value, provided the non-interface type implements the interface;
-
if the two dynamic types are identical and not comparable (slices, maps, functions, and structs or arrays that contain them), the comparison panics at run time. The compiler cannot catch it, because the dynamic type is unknown, and
go vetdoes not either; -
interface types can be map keys and used in
switchcases, and the panic risk comes with them; -
the predeclared constraint
comparableis satisfied by interface types since Go 1.20 (spec: "strictly comparable" versus "comparable"), because the comparison may panic at run time. Code that wants to guarantee no run-time panic must use a concrete type argument.
package main
import (
"fmt"
)
type point struct{ x, y int }
func main() {
var a, b any = point{1, 2}, point{1, 2}
fmt.Println(a == b) // same dynamic type, equal values
var c any = 1
var d any = int64(1)
fmt.Println(c == d) // different dynamic types: never equal
var e, f any = "x", "x"
fmt.Println(e == f)
// map keys of interface type work for comparable dynamic types
m := map[any]string{1: "int", "1": "string", point{0, 0}: "origin"}
fmt.Println(m[1], m["1"], m[point{0, 0}], len(m))
// comparing non-comparable dynamic types panics at run time
defer func() { fmt.Println("recovered:", recover()) }()
var g, h any = []int{1}, []int{1}
fmt.Println(g == h)
}
true
false
true
int string origin 3
recovered: runtime error: comparing uncomparable type []int
The message comparing uncomparable type []int is the run-time error to recognise. The same panic appears when an
interface holding a slice is used as a map key, as the key of a switch over interface values, or inside a struct
that is compared. See also Operators and Expressions
for which types are comparable and Maps for the key rules.
Accept interfaces, return structs
The best-known guideline about interfaces is Bodner’s chapter 7 formulation of an older Go proverb: accept interfaces, return structs. The Go code review wiki says the same (Go Code Review Comments: Interfaces): Go interfaces generally belong in the package that uses values of the interface type, not the package that implements those values; the implementing package returns concrete types, so new methods can be added without requiring extensive refactoring.
The consequences, with the shelf example below:
-
Functions take the narrowest interface that works.
Lendneeds onlyGet, so it declaresbookGetter, one method, and not the wholecatalog.Store. Any type with aGetmethod works, from the production*MemStoreto a one-line test double, and the function cannot accidentally callPut. -
Constructors return the concrete type.
NewMemStorereturns*MemStore; the caller may assign it to any interface that fits, and may also use the concrete type’s extra methods. A constructor that returns the interface hides the type, prevents adding methods, and forces every caller to depend on the interface the provider chose. -
Define the interface where it is used, not next to the implementation. A package should export an interface when it consumes it itself (like
io.Reader), or when many implementations are expected to live in many packages (http.Handler,sql/driver). -
Do not define an interface before there are two implementations or a test that needs one. The Go FAQ and the review comments both warn against interfaces "for mocking" in the package that provides the implementation.
-
Exceptions exist, and they are good ones: when a function must hide a concrete type for correctness (
errors.Newreturnserror,context.WithCancelreturnsContext), when a constructor returns one of several implementations chosen at run time (crypto/sha256.New() hash.Hash), and when the type is deliberately closed.
package main
import (
"fmt"
"example.com/shelf/catalog"
)
// The consumer declares only what it needs (one method), not the whole Store.
type bookGetter interface {
Get(isbn catalog.ISBN) (catalog.Book, bool)
}
// Lend accepts an interface...
func Lend(g bookGetter, isbn catalog.ISBN, member string) (catalog.Loan, error) {
if _, ok := g.Get(isbn); !ok {
return catalog.Loan{}, fmt.Errorf("lend %s: %w", isbn, errNotFound)
}
return catalog.Loan{Book: isbn, Member: member, State: catalog.OnLoan}, nil
}
var errNotFound = fmt.Errorf("book not found")
// ...and a constructor returns the concrete struct, never the interface.
func NewStore() *catalog.MemStore { return catalog.NewMemStore() }
// A test double needs only one method.
type fakeGetter map[catalog.ISBN]catalog.Book
func (f fakeGetter) Get(i catalog.ISBN) (catalog.Book, bool) { b, ok := f[i]; return b, ok }
func main() {
s := NewStore()
s.Put(catalog.Book{ISBN: "9780306406157", Title: "Dune"})
fmt.Println(Lend(s, "9780306406157", "ada"))
fmt.Println(Lend(s, "0000000000000", "ada"))
fmt.Println(Lend(fakeGetter{"1": {Title: "Fake"}}, "1", "bob"))
}
{9780306406157 ada on loan} <nil>
{ available} lend 0000000000000: book not found
{1 bob on loan} <nil>
The first call prints a loan; the second wraps errNotFound with %w, so the wrapping is visible to
errors.Is; the third gives a fake. The zero Loan printed as { available} is the zero value
(empty ISBN, empty member, Available). Bodner (chapter 7) builds the same argument with dependency injection by
interface; the pattern, together with function types as interface bridges, is the subject of
Composition and Object-Oriented Design, and
testing uses the same idea for fakes (Testing).
Compile-time assertions
Because satisfaction is implicit, a type that intends to satisfy an interface could silently stop doing so when someone renames a method. The FAQ’s idiom, How can I guarantee my type satisfies an interface?, turns that into a compile error with a blank-identifier variable declaration:
// the build fails if *MemStore stops satisfying Store
var _ Store = (*MemStore)(nil)
(*MemStore)(nil) is a typed nil pointer, a constant expression that allocates nothing; the variable is blank, so the
declaration produces no code. Use it when the relationship is part of the public contract (an exported type documented as
a Store, a handler, a marshaler), and skip it inside a package where any use of the type as the interface already
proves it. A failure reads exactly like the earlier errors (missing method Put, method Put has pointer receiver).
Embedding an interface in a struct
A struct can embed an interface value. All of the interface’s methods are promoted, forwarded to the wrapped value, and the struct can override just the ones it cares about: the cheapest decorator Go offers.
package main
import (
"fmt"
"example.com/shelf/catalog"
)
type cached struct {
catalog.Store // embedded interface: every method is promoted
hits int
}
// Override only Get; Put and All are forwarded to the wrapped Store.
func (c *cached) Get(i catalog.ISBN) (catalog.Book, bool) {
c.hits++
return c.Store.Get(i)
}
// compile-time assertions: the build fails if a method is missing or has the
// wrong signature, and nothing exists at run time.
var (
_ catalog.Store = (*cached)(nil)
_ catalog.Store = (*catalog.MemStore)(nil)
)
func main() {
c := &cached{Store: catalog.NewMemStore()}
c.Put(catalog.Book{ISBN: "1", Title: "Dune"})
c.Get("1")
c.Get("2")
fmt.Println(c.hits, len(c.All()))
// the zero value has a nil embedded interface: calling through it panics
defer func() { fmt.Println("recovered:", recover()) }()
var z cached
z.Put(catalog.Book{})
}
2 1
recovered: runtime error: invalid memory address or nil pointer dereference
Source: Effective Go: Embedding
cached satisfies catalog.Store although it declares only Get; Put and All come from the embedded field. The last
lines show the trap: the embedded interface of a zero-value cached is nil, so the promoted Put dereferences a nil
interface and panics. Always initialize the field (a constructor helps) or document that the zero value is not usable.
The pattern, and its limits (embedding is not inheritance: the wrapped Store never calls back into cached), are
detailed in Structs and Embedding and
Composition and Object-Oriented Design.
Optional interfaces
The comma-ok assertion to an interface type tests at run time whether a value has more capabilities than its static type promises. The standard library uses it for upgrades that keep the main interfaces small:
-
io.Copyprefersio.WriterToon the source andio.ReaderFromon the destination, falling back to a buffer loop; -
net/httphandlers check aResponseWriterforhttp.Flusher,http.Hijackerandhttp.Pusher(new code useshttp.NewResponseController, Go 1.20, which also unwraps middleware wrappers); -
fmtchecks forFormatter,Stringer,errorandGoStringer; -
errors.Isanderrors.Ascheck forIs(error) bool,As(any) boolandUnwrap() error(orUnwrap() []error); -
database/sqlchecks drivers for the optionaldriver.*Contextinterfaces.
package main
import (
"fmt"
"io"
"os"
"strings"
)
// flusher is an optional interface: a Writer may or may not support it.
type flusher interface{ Flush() error }
type logWriter struct{ w io.Writer }
func (l logWriter) Write(p []byte) (int, error) { return l.w.Write(p) }
func (l logWriter) Flush() error {
fmt.Fprintln(l.w, "[flushed]")
return nil
}
// write uses a plain Writer but upgrades when the value supports Flush.
func write(w io.Writer, s string) {
fmt.Fprintln(w, s)
if f, ok := w.(flusher); ok {
f.Flush()
}
}
func main() {
write(os.Stdout, "plain")
write(logWriter{os.Stdout}, "buffered")
// the standard library uses the same pattern: io.Copy upgrades to WriterTo
var sb strings.Builder
n, _ := io.Copy(&sb, strings.NewReader("copy me"))
_, isWT := any(strings.NewReader("")).(io.WriterTo)
fmt.Println(n, sb.String(), isWT)
}
plain
buffered
[flushed]
7 copy me true
Source: io: WriterTo
When you wrap a value (a middleware around a ResponseWriter, a Reader decorator) the wrapper hides the optional
methods of the inner value unless it forwards them; that is why http.ResponseController and the Unwrap
convention exist. Define an optional interface of your own when callers can function without it but a better path
exists when it is present.
Interfaces as type sets
Since Go 1.18 an interface also defines a type set that can be restricted with a union of types or the approximation
element ~T, and such an interface is a constraint for type parameters (spec:
General interfaces). Two kinds exist:
-
a basic interface: only methods (and embedded basic interfaces). It can be used as a variable type, a field type or a constraint;
-
a general interface: contains a union, a
~Tterm or a non-interface type, or embedscomparable. It can only be used as a constraint.
package main
import (
"fmt"
"strconv"
"example.com/shelf/catalog"
)
// Number is a general interface (type set), usable only as a constraint.
type Number interface {
~int | ~int64 | ~float64
}
func Sum[T Number](xs ...T) T {
var s T
for _, x := range xs {
s += x
}
return s
}
// A basic interface used as a constraint: T must have String.
func Join[T fmt.Stringer](xs []T) string {
out := ""
for i, x := range xs {
if i > 0 {
out += ", "
}
out += x.String()
}
return out
}
type Count int
func (c Count) String() string { return "#" + strconv.Itoa(int(c)) }
func main() {
fmt.Println(Sum(1, 2, 3), Sum(1.5, 2.5), Sum(Count(4), Count(5)))
fmt.Println(Join([]catalog.LoanState{catalog.Available, catalog.Overdue}))
fmt.Println(Join([]Count{1, 2}))
}
6 4 #9
available, overdue
#1, #2
Join uses the basic interface fmt.Stringer as a constraint: the type parameter must have String, and the call
is a static call, not an interface dispatch. Sum uses a union with ~int, so Count (whose underlying type is int)
qualifies. The error for misuse is a clear one:
package main
import "fmt"
type Number interface {
~int | ~float64
}
func main() {
var n Number = 3 // a type-set interface is only a constraint
fmt.Println(n)
}
./main.go:10:8: cannot use type Number outside a type constraint: interface contains type constraints
Further points, which Generics develops:
-
comparableis a general interface, usable only as a constraint; since Go 1.20 interface types satisfy it (with the run-time panic caveat above); -
Go 1.25 removed the notion of core types from the spec, so the constraint rules are stated in terms of type sets and the operations they permit (Go 1.25 release notes);
-
the choice "interface or type parameter" is a design decision: use a type parameter when the same code must work for several types and the operation is the same (
Sum, aSet[T comparable]); use an interface when the behaviour differs per type (Store); -
generic methods (Go 1.27) let a method declare its own type parameters, but a method with type parameters can never implement an interface method (Go 1.27 release notes), and interface methods may not declare type parameters.
package main
import (
"fmt"
"strings"
)
// Set stores keys; the generic method Map takes its own type parameter (Go 1.27).
type Set struct{ items []string }
func (s *Set) Add(x string) { s.items = append(s.items, x) }
func (s *Set) Convert[T any](f func(string) T) []T {
out := make([]T, 0, len(s.items))
for _, x := range s.items {
out = append(out, f(x))
}
return out
}
func main() {
s := &Set{}
s.Add("dune")
s.Add("ubik")
fmt.Println(s.Convert(strings.ToUpper))
fmt.Println(s.Convert(func(x string) int { return len(x) }))
}
[DUNE UBIK]
[4 4]
Source: Go 1.27 release notes
package main
import "fmt"
type Mapper interface {
Apply(x int) int
}
type doubler struct{}
func (doubler) Apply[T any](x T) T { return x }
func main() {
var m Mapper = doubler{}
fmt.Println(m.Apply(2))
}
./main.go:14:17: cannot use doubler{} (value of struct type doubler) as Mapper value in variable declaration: doubler does not implement Mapper (wrong type for method Apply)
have Apply[T any](T) T
want Apply(int) int
Source: Go 1.27 release notes
The have/want lines show that the generic method has the signature Apply[T any](T) T, which is not the signature
Apply(int) int the interface requires. The way around is to instantiate by hand: declare a concrete method with a
fixed type for the interface, and the generic one as an additional entry point.
Interfaces and reflection
Interface values are how reflect is entered: reflect.TypeOf(x) and reflect.ValueOf(x) take an any, and their
results report the dynamic type. reflect.TypeFor[T]() (Go 1.22) gets a reflect.Type for a static type, which is the
only way to describe an interface type itself, because a nil interface has no dynamic type; Type.Implements answers the
same question as a type assertion but for types, not values. errors.AsType (Go 1.26) is the generic version of
errors.As: it returns the value and a boolean and needs no pre-declared target variable.
package main
import (
"errors"
"fmt"
"reflect"
"example.com/shelf/catalog"
)
// errors.AsType[E error] requires E to implement error, so embed it.
type temporary interface {
error
Temporary() bool
}
type netErr struct{}
func (netErr) Error() string { return "connection reset" }
func (netErr) Temporary() bool { return true }
func main() {
// reflect: ask about interfaces at run time
stringer := reflect.TypeFor[fmt.Stringer]()
fmt.Println(stringer.Kind(), stringer.NumMethod(), stringer.Method(0).Name)
fmt.Println(reflect.TypeFor[catalog.LoanState]().Implements(stringer))
fmt.Println(reflect.TypeFor[catalog.Book]().Implements(stringer))
fmt.Println(reflect.TypeFor[*catalog.MemStore]().Implements(reflect.TypeFor[catalog.Store]()))
// errors.AsType (Go 1.26) with an interface type parameter
err := fmt.Errorf("fetch: %w", netErr{})
if t, ok := errors.AsType[temporary](err); ok {
fmt.Println("temporary:", t.Temporary())
}
var tgt temporary // the older errors.As form takes a pointer to the target
fmt.Println(errors.As(err, &tgt), tgt != nil)
}
interface 1 String
true
false
true
temporary: true
true true
Source: reflect: TypeFor and errors: AsType
Note that errors.AsType[E error] requires its type argument to implement error, so the interface temporary
embeds error; the older errors.As takes a pointer to any interface type. More on reflect is in
Reflection.
Vet and the compiler
The compiler catches the structural errors (missing method, wrong signature, pointer receiver, impossible assertion,
impossible or duplicate switch case, a type-set interface used as a type). go vet adds checks that need a little
more knowledge (the full list is in
Formatting, vet, fix and Static Analysis):
| Analyzer | What it reports for interfaces |
|---|---|
|
a method with a well-known name ( |
|
an assertion between two interfaces that no type could ever satisfy (conflicting method signatures) |
|
|
|
a value containing a |
package main
import "fmt"
type number int
// The method name ReadByte is reserved by io.ByteReader: vet checks the signature.
func (n number) ReadByte() int { return int(n) }
func main() {
fmt.Println(number(1).ReadByte())
}
./main.go:8:17: method ReadByte() int should have signature ReadByte() (byte, error)
Source: cmd/vet
package main
import (
"errors"
"fmt"
"os"
)
func main() {
_, err := os.Open("/nonexistent")
var pe os.PathError
fmt.Println(errors.As(err, pe)) // the target must be a pointer
}
./main.go:12:14: second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type
Source: errorsas analyzer
package main
import (
"fmt"
"io"
)
func main() {
var r io.Reader
// io.Reader.Read has a different signature from the asserted interface,
// so no type can ever satisfy both.
if x, ok := r.(interface{ Read() }); ok {
fmt.Println(x)
}
}
./main.go:12:17: impossible type assertion: no type can implement both io.Reader and interface{Read()} (conflicting types for Read method)
Source: ifaceassert analyzer
Analyzers from golang.org/x/tools outside the default go vet set (for example nilness, which can flag a typed
nil returned as error) can be run with gopls or standalone drivers, and staticcheck adds more; see the same
formatting-and-analysis page.
Pointers to interfaces and other pitfalls
-
A pointer to an interface is almost never what you want. A value of type
*io.Readerhas no methods: the pointer is not an interface, so the call does not compile. The FAQ’s When should I use a pointer to an interface? entry explains: pass the interface value itself (it already holds a pointer if the dynamic value is a pointer).package main import ( "fmt" "io" "strings" ) func read(r *io.Reader) { buf := make([]byte, 4) n, _ := r.Read(buf) // a pointer to an interface has no methods fmt.Println(n) } func main() { var r io.Reader = strings.NewReader("dune") read(&r) }./main.go:11:12: r.Read undefined (type *io.Reader is pointer to interface, not interface) -
An interface holds a copy. Assigning a struct to an interface copies it; later changes to the original are not seen through the interface, and methods with pointer receivers cannot be reached (they need the address of the original). Store a pointer when the interface must see the live object.
-
A method value on an interface also binds a copy for value receivers.
s2.Speakin the earlier example did not see the later rename ofd. -
anyswallows type information. Code full ofanyand type switches is a sign that an interface method or a generic function was missed. Prefer a named small interface or a type parameter. -
An interface with too many methods is hard to implement and hard to fake. Split it; embed only when a consumer truly needs the union.
-
Do not return a concrete pointer as an interface in a conditional path (the typed-nil trap above), and do not compare
errwith a concrete*MyErrorvariable: useerrors.Asorerrors.Is. -
Unexported methods seal an interface.
interface{ isNode() }can only be implemented in its own package, which is howgo/astanddatabase/sqlkeep closed sets of types; type switches over such a sealed interface are then exhaustive by convention, although the compiler does not check it. -
Interface satisfaction ignores the package for exported names but not for unexported ones. Two packages that each declare an unexported
close()method do not satisfy each other’s interfaces. -
Methods from the embedded field of a non-pointer struct are in the struct’s method set only if the field’s receiver allows it. If
Shas a pointer-receiver method, thenstruct{ S }(a value) does not satisfy an interface needing it, whilestruct{ *S }and*struct{ S }do. -
Concurrent use. An interface value is two words and is not updated atomically: two goroutines assigning to the same interface variable without synchronization is a data race that can tear the pair (see Memory Model and Data Races).
atomic.Valueandatomic.Pointer[T]exist for that case.
Comparing with other languages
| Concept | Go | In other languages |
|---|---|---|
Declaring satisfaction |
none: structural, checked by the compiler |
Java/C#: |
Who may add a conformance |
only the package defining the type (methods must be in the same package) |
Rust: the crate with the trait or the type; Swift and C#: extensions in other modules |
Default method bodies |
none; use embedding of a helper struct or a function |
Java/C#/Swift/Rust: default or protocol-extension methods |
Static dispatch |
via type parameters (the call is resolved per instantiation of the gc shapes) |
Rust monomorphization; C++ templates |
Dynamic dispatch |
an itab and an indirect call through the interface value |
virtual calls; Rust |
Associated types or constants |
none in interfaces |
Rust, Swift: yes |
Run-time type inspection |
type assertions, type switches, |
instanceof/pattern matching, |
Null |
the nil interface (and the typed-nil trap) |
null references; Rust has no null |
The mapping "class, interface, inheritance" to "struct, interface, embedding" is spelled out in Design Philosophy and Coming from Other Languages.
Where the books are outdated or wrong
Bodner’s chapter 7 introduces interfaces, "accept interfaces, return structs", embedding in interfaces, the nil interface and the empty interface, type assertions and switches, function types as interface bridges and dependency injection; those parts remain right. The details that have moved on since the book (written for Go 1.15) are:
-
the book writes the empty interface as
interface{}; since Go 1.18anyis the idiom and the code in this page uses it; -
older advice that an interface may not embed two interfaces declaring the same method no longer applies: since Go 1.14 that is allowed when the signatures are identical;
-
the generics chapter (chapter 15) uses the draft "type list" syntax and the Go2Go playground; the shipped design uses interfaces as type sets with unions and
~T(Go 1.18), better inference (1.21), thecmp.Orderedconstraint, and generic methods (1.27, which still cannot implement interface methods); -
comparison of interface values and the
comparableconstraint changed in Go 1.20 (interfaces satisfycomparable); -
errors.Aswith an interface target anderrors.AsType(Go 1.26) are the current way to ask for a capability of an error in a chain, instead of a type assertion on the outermost error value; -
reflect.TypeFor[T]()(Go 1.22) replacesreflect.TypeOf((*T)(nil)).Elem()for describing interface types.
Vivien’s chapter 8 covers interface types, implicit implementation, subtyping, interface embedding, the empty
interface and type assertions. Beyond the same interface{} versus any change, its rule that embedded interfaces may
not overlap predates Go 1.14, and chapter 1 presents Stringer as if it were built in (it is fmt.Stringer; the
predeclared interfaces are error, any and comparable). Go has no subtyping between struct types, only
assignability to interface types, which is worth keeping in mind when reading the book’s "subtype" wording. The
error coverage in Vivien’s chapters 1 and 5 also needs the typed-nil caveat explained on this page.
Version notes
-
Go 1.0: the
errorinterface; implicit interface satisfaction since the first release. -
Go 1.14: overlapping method sets in embedded interfaces are allowed.
-
Go 1.18:
any, type-set interfaces with unions and~T,comparable; a basic interface can still be used as a type. -
Go 1.20: interface types satisfy
comparable;http.ResponseController. -
Go 1.21:
slices.SortFuncand relatives reduce the need forsort.Interface. -
Go 1.22:
reflect.TypeFor. -
Go 1.25: core types removed from the specification.
-
Go 1.26:
errors.AsType. -
Go 1.27: generic methods (which cannot implement interface methods);
math/rand/v2gains a generic method.
References
-
The Go Programming Language Specification: Implementing an interface
-
The Go Programming Language Specification: Comparison operators
-
The Go Programming Language Specification: General interfaces
-
Go 1.14, Go 1.18, Go 1.20, Go 1.26 and Go 1.27 release notes
-
fmt.Stringer, io, sort.Interface, net/http.Handler, encoding.TextMarshaler, errors.AsType and reflect.TypeFor