Interfaces
Go interfaces define a set of methods that a type must implement. Unlike other languages, Go interfaces are implemented implicitly. Without any implements keyword, if a type has all the methods an interface requires, it automatically satisfies that interface.
Interface Basicsβ
An interface is a collection of method signatures.
package main
import (
"fmt"
"math"
)
// Interface definition
type Shape interface {
Area() float64
Perimeter() float64
}
type Circle struct {
Radius float64
}
// Circle implements Shape β automatically, no declaration needed
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// Accepts a Shape interface parameter
func printShapeInfo(s Shape) {
fmt.Printf("Area: %.2f, Perimeter: %.2f\n", s.Area(), s.Perimeter())
}
func main() {
shapes := []Shape{
Circle{Radius: 5},
Rectangle{Width: 4, Height: 6},
Circle{Radius: 3},
}
for _, s := range shapes {
printShapeInfo(s)
}
// Area: 78.54, Perimeter: 31.42
// Area: 24.00, Perimeter: 20.00
// Area: 28.27, Perimeter: 18.85
}
Benefits of Implicit Implementationβ
Go's implicit interface implementation makes code more flexible. Even interfaces defined later are automatically implemented by existing types.
package main
import (
"fmt"
"strings"
)
// Existing types automatically implement later-defined interfaces
type Describer interface {
Describe() string
}
type Dog struct {
Name string
Breed string
}
func (d Dog) Describe() string {
return fmt.Sprintf("%s is a %s", d.Name, d.Breed)
}
type Car struct {
Make string
Model string
Year int
}
func (c Car) Describe() string {
return fmt.Sprintf("%d %s %s", c.Year, c.Make, c.Model)
}
func printAll(items []Describer) {
for _, item := range items {
fmt.Println(item.Describe())
}
}
func main() {
items := []Describer{
Dog{Name: "Rex", Breed: "Jindo"},
Car{Make: "Hyundai", Model: "Elantra", Year: 2024},
Dog{Name: "Luna", Breed: "Poodle"},
}
printAll(items)
// strings.Builder implements multiple interfaces without explicit declarations
var sb strings.Builder
fmt.Fprintln(&sb, "Hello, Go!")
fmt.Fprintln(&sb, "Interfaces are implicit")
fmt.Print(sb.String())
}
Interface Embeddingβ
Interfaces can embed other interfaces.
package main
import (
"fmt"
"io"
"strings"
)
// Basic interfaces
type Reader interface {
Read() string
}
type Writer interface {
Write(s string)
}
// Interface embedding β includes both Reader and Writer
type ReadWriter interface {
Reader
Writer
}
// With additional method
type ReadWriteCloser interface {
ReadWriter
Close() error
}
type Buffer struct {
data strings.Builder
}
func (b *Buffer) Read() string {
return b.data.String()
}
func (b *Buffer) Write(s string) {
b.data.WriteString(s)
}
func (b *Buffer) Close() error {
b.data.Reset()
return nil
}
func useReadWriter(rw ReadWriter) {
rw.Write("Hello, ")
rw.Write("World!")
fmt.Println(rw.Read())
}
func main() {
buf := &Buffer{}
useReadWriter(buf) // "Hello, World!"
buf.Close()
// Standard library also uses interface embedding
// io.ReadWriter = io.Reader + io.Writer
var rw io.ReadWriter = strings.NewReader("test")
_ = rw
fmt.Println("Standard library also uses interface embedding")
}
The Empty Interface β anyβ
interface{} (aliased as any since Go 1.18) has no methods, so every type implements it.
package main
import "fmt"
// any = interface{} (Go 1.18+)
func printAnything(v any) {
fmt.Printf("value: %v, type: %T\n", v, v)
}
func storeAll(items ...any) []any {
return items
}
// Generic container (using any)
type Container struct {
items []any
}
func (c *Container) Add(item any) {
c.items = append(c.items, item)
}
func (c *Container) Get(index int) any {
if index < 0 || index >= len(c.items) {
return nil
}
return c.items[index]
}
func (c *Container) Len() int {
return len(c.items)
}
func main() {
printAnything(42) // value: 42, type: int
printAnything("hello") // value: hello, type: string
printAnything(3.14) // value: 3.14, type: float64
printAnything([]int{1, 2}) // value: [1 2], type: []int
printAnything(nil) // value: <nil>, type: <nil>
items := storeAll(1, "two", 3.0, true, []byte("bytes"))
for _, item := range items {
fmt.Printf("%T: %v\n", item, item)
}
c := &Container{}
c.Add(42)
c.Add("hello")
c.Add([]float64{1.1, 2.2})
// To extract a concrete type from any, use type assertion (see ch7.2)
if n, ok := c.Get(0).(int); ok {
fmt.Println("integer:", n*2)
}
}
Internal Structure of Interface Valuesβ
An interface value is internally composed of a (type, value) pair.
package main
import (
"fmt"
"reflect"
)
type Doer interface {
Do() string
}
type TypeA struct{ name string }
type TypeB struct{ value int }
func (a TypeA) Do() string { return "A: " + a.name }
func (b TypeB) Do() string { return fmt.Sprintf("B: %d", b.value) }
func inspectInterface(d Doer) {
fmt.Printf("value: %v\n", d)
fmt.Printf("type: %T\n", d)
v := reflect.ValueOf(d)
fmt.Printf("reflect type: %v\n", v.Type())
fmt.Printf("reflect value: %v\n", v)
fmt.Println()
}
func main() {
var d Doer
fmt.Println("nil interface:", d == nil) // true
d = TypeA{name: "foo"}
inspectInterface(d)
d = TypeB{value: 42}
inspectInterface(d)
// nil interface pitfall
var a *TypeA = nil
d = a // (type=*TypeA, value=nil) β interface is NOT nil!
fmt.Println("after nil pointer assign:", d == nil) // false!
// Correct nil check
if d != nil {
if a, ok := d.(*TypeA); ok && a != nil {
fmt.Println(a.Do())
}
}
}
Practical Example: Plugin Systemβ
package main
import (
"fmt"
"sort"
"strings"
)
// Plugin interface
type Plugin interface {
Name() string
Execute(input string) string
}
// Plugin registry
type Registry struct {
plugins map[string]Plugin
}
func NewRegistry() *Registry {
return &Registry{plugins: make(map[string]Plugin)}
}
func (r *Registry) Register(p Plugin) {
r.plugins[p.Name()] = p
}
func (r *Registry) Execute(name, input string) (string, bool) {
p, ok := r.plugins[name]
if !ok {
return "", false
}
return p.Execute(input), true
}
func (r *Registry) ListPlugins() []string {
names := make([]string, 0, len(r.plugins))
for name := range r.plugins {
names = append(names, name)
}
sort.Strings(names)
return names
}
// Plugin implementations
type UpperPlugin struct{}
func (p UpperPlugin) Name() string { return "upper" }
func (p UpperPlugin) Execute(s string) string { return strings.ToUpper(s) }
type ReversePlugin struct{}
func (p ReversePlugin) Name() string { return "reverse" }
func (p ReversePlugin) Execute(s string) string {
runes := []rune(s)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
return string(runes)
}
type RepeatPlugin struct {
Times int
}
func (p RepeatPlugin) Name() string { return "repeat" }
func (p RepeatPlugin) Execute(s string) string { return strings.Repeat(s, p.Times) }
func main() {
reg := NewRegistry()
reg.Register(UpperPlugin{})
reg.Register(ReversePlugin{})
reg.Register(RepeatPlugin{Times: 3})
fmt.Println("Registered plugins:", reg.ListPlugins())
inputs := []struct {
plugin string
input string
}{
{"upper", "hello world"},
{"reverse", "golang"},
{"repeat", "Go! "},
{"unknown", "test"},
}
for _, tc := range inputs {
result, ok := reg.Execute(tc.plugin, tc.input)
if ok {
fmt.Printf("[%s] %q β %q\n", tc.plugin, tc.input, result)
} else {
fmt.Printf("[%s] plugin not found\n", tc.plugin)
}
}
}
Interface Design Principlesβ
package main
import "fmt"
// Bad: interface too large
type BigInterface interface {
Read() string
Write(s string)
Close() error
Flush() error
Seek(offset int) error
Len() int
// ... too many methods
}
// Good: small, focused interfaces
type Reader interface {
Read() string
}
type Writer interface {
Write(s string)
}
type Closer interface {
Close() error
}
// Compose as needed
type ReadCloser interface {
Reader
Closer
}
// Single-method interfaces β most powerful
type Stringer interface {
String() string
}
type Logger interface {
Log(msg string)
}
// Function type implementing an interface
type LoggerFunc func(msg string)
func (f LoggerFunc) Log(msg string) {
f(msg)
}
func doWork(l Logger) {
l.Log("work started")
l.Log("work finished")
}
func main() {
// Implement with a function
doWork(LoggerFunc(func(msg string) {
fmt.Println("[LOG]", msg)
}))
// Implement with a struct
doWork(LoggerFunc(func(msg string) {
fmt.Printf("[FILE] %s\n", msg)
}))
}
Key Summary
- Go interfaces use implicit implementationβ no
implementsdeclaration needed- Interface internals: (dynamic type, dynamic value) pair
any(interface{}) can hold any type β the empty interface- Design interfaces smallβ single-method interfaces are the most flexible
- nil interface β interface holding a nil pointer (watch out!)
- Embed interfaces to compose larger interfaces