Interfaces are Go's main tool for abstraction, testing seams and plugging into the standard library.
An interface type lists method signatures. Any type that has those methods satisfies it implicitly — there's no implements keyword:
type Shape interface { Area() float64 }Every type with an Area() float64 method is a Shape, including types written before the interface existed. This lets consumers define the small interface they need. The idiom is "accept interfaces, return concrete types", and to keep interfaces small: the standard library's io.Reader (Read(p []byte) (int, error)), io.Writer and fmt.Stringer (String() string) each have one method, which is why so many types compose with them.
An interface value holds a (dynamic type, dynamic value) pair. any (alias for interface{}) has no methods, so it can hold anything — use it sparingly, since you lose static checking.
To get the concrete value back:
- Type assertion:
s, ok := v.(fmt.Stringer). Withoutok, a failed assertion panics. - Type switch:
switch x := v.(type) { case int: ...; case string: ... }—xhas the case's type in each branch.
A frequent gotcha: an interface is nil only if both type and value are nil. Returning a nil *MyErr as an error produces a non-nil interface. Return a literal nil instead.
To check at compile time that a type implements an interface, write var _ Shape = (*Square)(nil).
Example
package main
import (
"fmt"
"math"
)
type Shape interface {
Area() float64
}
type Circle struct{ R float64 }
type Square struct{ S float64 }
func (c Circle) Area() float64 { return math.Pi * c.R * c.R }
func (s Square) Area() float64 { return s.S * s.S }
func (s Square) String() string { return fmt.Sprintf("square(%g)", s.S) }
func describe(v any) string {
switch x := v.(type) {
case fmt.Stringer:
return x.String()
case Shape:
return fmt.Sprintf("shape with area %.1f", x.Area())
default:
return "unknown"
}
}
func main() {
for _, s := range []Shape{Circle{1}, Square{2}} {
fmt.Println(describe(s)) // shape with area 3.1, then square(2)
}
}Remember
- Types satisfy interfaces implicitly by having the methods
- Keep interfaces small and define them where they are used
- Use comma-ok assertions or type switches to recover concrete types
Watch out for
- Returning a typed nil pointer as an interface — the interface is not nil.