Interfaces
Go interfaces are implicit — a type satisfies an interface by having the right methods, no implements keyword. The empty interface (any / interface{}) holds any value; specific interfaces enable polymorphism.
Define, satisfy, embed, type assert
EXAMPLE
package main
import (
"fmt"
"io"
"os"
"strings"
)
// 1) Define an interface
type Stringer interface {
String() string
}
// 2) Implicit implementation — no `implements` keyword
type User struct{ Name, Email string }
func (u User) String() string {
return fmt.Sprintf("%s <%s>", u.Name, u.Email)
}
// User satisfies Stringer because it has the right method.
func main() {
var s Stringer = User{Name: "Ada", Email: "a@x.com"}
fmt.Println(s.String())
}
// 3) Polymorphism — accept any Stringer
func printAll(list []Stringer) {
for _, s := range list {
fmt.Println(s.String())
}
}
// 4) Standard-library interfaces you already know
// io.Reader — Read(p []byte) (n int, err error)
// io.Writer — Write(p []byte) (n int, err error)
// io.Closer — Close() error
// fmt.Stringer — String() string
// error — Error() string
// sort.Interface — Len, Less, Swap
func copyTo(dst io.Writer, src io.Reader) error {
_, err := io.Copy(dst, src)
return err
}
// Pass any of: os.Stdout, *os.File, *bytes.Buffer, http.ResponseWriter, ...
copyTo(os.Stdout, strings.NewReader("hello\n"))
// 5) Interface composition — embed smaller interfaces
type ReadWriter interface {
io.Reader
io.Writer
}
type ReadWriteCloser interface {
io.Reader
io.Writer
io.Closer
}
// 6) Type assertion — extract the concrete type
var w io.Writer = os.Stdout
if f, ok := w.(*os.File); ok {
fmt.Println("have file:", f.Name())
}
// 7) Type switch — branch on concrete type
func describe(v any) string {
switch x := v.(type) {
case int: return fmt.Sprintf("int %d", x)
case string: return fmt.Sprintf("string %q", x)
case fmt.Stringer:return x.String()
case nil: return "nil"
default: return fmt.Sprintf("%T %v", x, x)
}
}
// 8) Empty interface — any (Go 1.18+ alias)
var v any = 42
v = "hello"
v = []int{1, 2, 3}
// 9) Nil interfaces — a subtle gotcha
var e error = nil
fmt.Println(e == nil) // true
var bad *MyError = nil
e = bad // interface now has TYPE info but no VALUE
fmt.Println(e == nil) // FALSE — surprise!
// Fix: return nil error explicitly when there's no error:
func work() error {
var err *MyError
// ...
if err == nil {
return nil // not `return err`
}
return err
}
// 10) Accept interfaces, return concrete types
// Idiomatic Go: function args should be the smallest interface that works.
func CalcChecksum(r io.Reader) (uint32, error) { /* … */ }
// vs `func CalcChecksum(f *os.File)` — the interface version takes strings.Reader, http body, etc.
// 11) Methods on pointer vs value — affects interface satisfaction
type Counter struct{ n int }
func (c *Counter) Inc() { c.n++ } // pointer receiver
func (c Counter) Value() int { return c.n } // value receiver
type Incrementer interface { Inc() }
var i Incrementer = &Counter{} // OK — *Counter has Inc()
// var i Incrementer = Counter{} // ERROR — Counter (value) doesn't satisfy
// 12) Generics (Go 1.18+) sometimes replace interfaces
func Map[T, U any](in []T, fn func(T) U) []U {
out := make([]U, len(in))
for i, v := range in {
out[i] = fn(v)
}
return out
}
Why it matters
Idiomatic Go: accept the smallest interface that works (io.Reader), return concrete types. Implicit satisfaction means new types just plug in — no inheritance ceremony.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
type Stringer interface {
String() string
}
func (u User) String() string { return u.Name }
Try it Yourself »
Discussion
Loading…