31. Composing interfaces
Small interfaces are the goal, but sometimes you need several capabilities at once. Go's answer isn't a bigger interface — it's embedding small ones into a combination, the same composition idea you saw with structs.
Embedding interfaces
package main
import "fmt"
type Reader interface {
Read() string
}
type Writer interface {
Write(s string) int
}
type ReadWriter interface {
Reader
Writer
}
type Memory struct {
data string
}
func (m *Memory) Read() string { return m.data }
func (m *Memory) Write(s string) int {
m.data += s
return len(s)
}
func main() {
var rw ReadWriter = &Memory{}
rw.Write("hello ")
rw.Write("world")
fmt.Println(rw.Read())
var r Reader = rw
fmt.Println(r.Read())
}
ReadWriter lists two interfaces instead of two methods; it means "has
everything Reader has, plus everything Writer has".
Then look at var r Reader = rw: a ReadWriter can be assigned to a
Reader variable for free, because anything satisfying the bigger set
necessarily satisfies the smaller one. Interfaces flow downward toward
less capability, automatically.
This is exactly how the standard library is built. io.ReadWriter,
io.ReadCloser and io.ReadWriteCloser are all just combinations of
io.Reader, io.Writer and io.Closer.
Define the interface where you use it
The habit that makes Go codebases pleasant: a package declares the narrow interface it needs, rather than importing somebody else's wide one.
package main
import "fmt"
type Logger interface {
Log(msg string)
}
type Metrics interface {
Count(name string)
}
type ConsoleTools struct {
prefix string
counts map[string]int
}
func (c *ConsoleTools) Log(msg string) { fmt.Println(c.prefix + msg) }
func (c *ConsoleTools) Count(name string) {
if c.counts == nil {
c.counts = map[string]int{}
}
c.counts[name]++
}
func (c *ConsoleTools) Report(name string) int { return c.counts[name] }
func processOrder(id int, log Logger) {
log.Log(fmt.Sprintf("processing order %d", id))
}
func main() {
tools := &ConsoleTools{prefix: "[app] "}
processOrder(1, tools)
processOrder(2, tools)
tools.Count("orders")
tools.Count("orders")
fmt.Println("orders counted:", tools.Report("orders"))
}
processOrder asks for a Logger — one method — even though the thing it
gets can do much more. That's the smallest honest contract, so it's the
easiest to satisfy in a test:
package main
import "fmt"
type Logger interface {
Log(msg string)
}
type fakeLogger struct {
lines []string
}
func (f *fakeLogger) Log(msg string) { f.lines = append(f.lines, msg) }
func processOrder(id int, log Logger) {
log.Log(fmt.Sprintf("processing order %d", id))
}
func main() {
fake := &fakeLogger{}
processOrder(1, fake)
processOrder(2, fake)
fmt.Println("captured", len(fake.lines), "log lines")
for _, l := range fake.lines {
fmt.Println(" -", l)
}
}
Eight lines and you have a test double. No mocking framework, no code generation — because the interface has one method and satisfaction is implicit. Design for that and testing stays cheap.
Embedding an interface in a struct
A struct can embed an interface, which means "I have these methods, forwarded to whatever is in this field":
package main
import "fmt"
type Store interface {
Get(key string) string
Put(key, value string)
}
type MapStore struct {
m map[string]string
}
func (s *MapStore) Get(key string) string { return s.m[key] }
func (s *MapStore) Put(key, value string) { s.m[key] = value }
type LoggingStore struct {
Store
}
func (l LoggingStore) Put(key, value string) {
fmt.Printf("PUT %s=%s\n", key, value)
l.Store.Put(key, value)
}
func main() {
base := &MapStore{m: map[string]string{}}
store := LoggingStore{Store: base}
store.Put("lang", "go")
store.Put("year", "2009")
fmt.Println(store.Get("lang"))
}
LoggingStore satisfies Store while implementing only one of its
methods — Get is promoted straight through from the embedded interface.
That's the decorator pattern with no boilerplate, and it's how middleware
gets written in Go.
Careful, though: if the embedded interface is nil and somebody calls a method you didn't override, it panics at runtime rather than failing to compile. Wrap what you're given, and don't leave the field empty.
The nil interface trap
Save this one; it will cost you an afternoon otherwise.
package main
import "fmt"
type Speaker interface {
Speak() string
}
type Dog struct{}
func (d *Dog) Speak() string { return "woof" }
func main() {
var s Speaker
fmt.Println("untouched interface is nil:", s == nil)
var d *Dog
s = d
fmt.Println("after assigning a nil *Dog:", s == nil)
fmt.Printf("type inside: %T\n", s)
}
The second line prints false. An interface value is a (type, value) pair,
and it's nil only when both halves are nil. Assigning a nil *Dog gives
you the pair (*Dog, nil) — a non-nil interface holding a nil pointer.
Where it bites is error returns:
type MyError struct{ Msg string }
func (e *MyError) Error() string { return e.Msg }
func doWork() error {
var err *MyError // nil
if somethingFails() {
err = &MyError{"boom"}
}
return err // ALWAYS non-nil as an error!
}
// caller: if err := doWork(); err != nil { ... } <- always true
The fix is to return the concrete type only when it's real:
func doWork() error {
if somethingFails() {
return &MyError{"boom"}
}
return nil // an untyped nil — genuinely nil
}
Rule of thumb: declare functions as returning error, and return a literal
nil for success. Never return a typed nil pointer as an interface.
Your turn
Complete TimestampWriter so it satisfies Printer by embedding one and
overriding Print to prefix the message. The program should print:
[t] hello
[t] world
package main
import "fmt"
type Printer interface {
Print(msg string)
}
type Console struct{}
func (Console) Print(msg string) { fmt.Println(msg) }
type TimestampWriter struct {
// embed a Printer here
}
// override Print to prefix "[t] " and delegate
func main() {
w := TimestampWriter{Printer: Console{}}
w.Print("hello")
w.Print("world")
}
package main
import "fmt"
type Printer interface {
Print(msg string)
}
type Console struct{}
func (Console) Print(msg string) { fmt.Println(msg) }
type TimestampWriter struct {
Printer
}
func (t TimestampWriter) Print(msg string) {
t.Printer.Print("[t] " + msg)
}
func main() {
w := TimestampWriter{Printer: Console{}}
w.Print("hello")
w.Print("world")
}
Next: the handful of standard-library interfaces you'll actually implement.