An interface is a set of methods
An interface type lists method signatures. Any type that has those methods satisfies the interface, with no declaration saying so.
Neither Rect nor Circle mentions Shape. That is implicit implementation, the defining feature of Go interfaces. It means you can define an interface in your package that types from other packages already satisfy, without touching their code.
Small interfaces from the standard library
Go code favors interfaces with one or two methods. The most important ones:
| Interface | Method | Used by |
|---|---|---|
fmt.Stringer | String() string | fmt printing |
error | Error() string | every function that can fail |
io.Reader | Read(p []byte) (n int, err error) | files, network, gzip, HTTP bodies |
io.Writer | Write(p []byte) (n int, err error) | files, buffers, hashes, HTTP responses |
sort.Interface | Len, Less, Swap | the sort package |
http.Handler | ServeHTTP(w, r) | net/http |
Because io.Reader has one method, dozens of types implement it, and any function that takes an io.Reader works with all of them:
The Go proverb is "the bigger the interface, the weaker the abstraction". Larger interfaces are built by combining small ones: io.ReadWriter is Reader plus Writer, written by embedding one interface in another.
any: the empty interface
interface{} has no methods, so every type satisfies it. Go 1.18 added any as an alias; they are identical.
An any value can hold anything but you can do almost nothing with it until you recover the concrete type with a type assertion or type switch. Prefer a real interface or generics when the set of types is known. any fits truly dynamic data such as decoded JSON of unknown shape, and printing.
What an interface value contains
An interface value is a pair: a dynamic type and a dynamic value. var s Shape = Rect{3, 4} stores the type Rect and a copy of the value. Calling s.Area() looks up Rect's method at run time.
An interface is nil only when both parts are empty. That rule causes the most confusing bug in Go.
The nil interface gotcha
A nil pointer stored in an interface makes a non-nil interface.
Output:
false
*main.MyError true
true
validate(true) returns an error interface holding type *MyError and value nil. The interface has a type, so it is not equal to nil, and the caller's if err != nil branch runs. Calling err.Error() there would then panic on the nil receiver's field access.
The fix is simple: declare the variable as error, not as the concrete pointer type, or return a literal nil on the success path. Never return a concrete error pointer type from a function whose result is error. The same trap applies to any interface, not only errors.
Checking that a type implements an interface
Implementation is checked where a value is assigned to an interface. If no code does that yet, a mistake in a method signature goes unnoticed. A blank package-level assignment makes the check explicit:
var _ io.Writer = (*LogWriter)(nil)
var _ fmt.Stringer = Temp(0)
This costs nothing at run time. If *LogWriter has Write(p []byte) error instead of Write(p []byte) (int, error), the build fails:
cannot use (*LogWriter)(nil) (value of type *LogWriter) as io.Writer value in variable declaration: *LogWriter does not implement io.Writer (wrong type for method Write)
have Write([]byte) error
want Write([]byte) (int, error)
Pointer receivers and interfaces
If a method has a pointer receiver, only the pointer type has that method. *Counter satisfies an interface through it; Counter does not. The compiler says Counter does not implement Incrementer (method Inc has pointer receiver). Store &Counter{} in the interface. The methods page explains method sets.
Accept interfaces, return structs
A common Go guideline: functions should take interface parameters and return concrete types.
- Accepting an interface lets callers pass anything that fits, including test fakes. A function that reads data should take an
io.Reader, not an*os.File. - Returning a concrete type lets callers use all of its methods and fields, and avoids the nil interface trap.
os.Openreturns*os.File, notio.Reader.
A related habit: define interfaces where they are used, not where they are implemented. If your service needs something that can Get(id) a user, declare a one-method interface in your service package, and let the database package just export its struct.
Comparing interface values
Two interface values are equal when their dynamic types are identical and their dynamic values are equal. If the dynamic type is not comparable (a slice, a map), == compiles but panics at run time: runtime error: comparing uncomparable type []int.
Common mistakes
- Returning a typed nil pointer as an interface. Return literal
nil. - Interfaces too early. Write the concrete type first. Add an interface when a second implementation or a test needs one.
- Pointer to interface.
*io.Readeris almost never right. An interface already holds a pointer when you store one in it. - Big interfaces. Ten-method interfaces are hard to implement and hard to fake. Split them.
Frequently Asked Questions
How do you implement an interface in Go?
Define the methods the interface lists, with the same names and signatures, on your type. There is no implements keyword. If *File has Read(p []byte) (int, error), it is an io.Reader, automatically. The compiler checks this wherever you assign the value to the interface type.
What is the empty interface or any in Go?
interface{} has no methods, so every type satisfies it. Since Go 1.18, any is a built-in alias for interface{}. A value of type any can hold anything, but you need a type assertion or type switch to get a concrete type back out.
Why is my Go interface not nil when I assigned a nil pointer?
An interface value holds a type and a value. Assigning a nil *MyError to an error gives an interface whose type is *MyError and whose value is nil, and that interface is not equal to nil. Return a literal nil instead of a typed nil pointer when there is no error.
How do I check at compile time that a type implements an interface?
Add a blank assignment at package level: var _ io.Reader = (*MyReader)(nil). If *MyReader is missing a method, the build fails with a message naming the missing method.