29. Satisfying an interface: the rules
Implicit satisfaction feels like magic until it doesn't work and you can't see why. There are exactly three rules to know, and one of them — the pointer-receiver rule — accounts for most of the confusion.
Rule 1: every method must match exactly
package main
import "fmt"
type Closer interface {
Close() error
}
type File struct{ Name string }
func (f File) Close() error {
fmt.Println("closing", f.Name)
return nil
}
func main() {
var c Closer = File{Name: "data.txt"}
fmt.Println(c.Close())
}
Name, parameters and results must all line up. A method Close() with no
result does not satisfy Close() error — Go doesn't coerce signatures,
and the compile error tells you precisely which method is wrong or missing.
Rule 2: the compiler checks at assignment
Satisfaction is verified where you assign a concrete value to an interface, which is the moment you'd get an error. To assert it deliberately — so you find out at compile time even when nothing assigns yet — Go programmers use this line:
package main
import "fmt"
type Closer interface {
Close() error
}
type File struct{ Name string }
func (f File) Close() error { return nil }
var _ Closer = File{}
func main() {
fmt.Println("File satisfies Closer, checked at compile time")
}
var _ Closer = File{} declares a throwaway variable of the interface type
and assigns a File to it. It costs nothing at runtime and breaks the build
the moment File stops satisfying Closer. You'll see it near the top of
files in real Go code — now you know what it's for.
Rule 3: pointer receivers mean pointers only
This is the one:
package main
import "fmt"
type Counter interface {
Inc()
Value() int
}
type Basic struct{ n int }
func (b *Basic) Inc() { b.n++ }
func (b *Basic) Value() int { return b.n }
func main() {
var c Counter = &Basic{}
c.Inc()
c.Inc()
fmt.Println(c.Value())
// var broken Counter = Basic{} // won't compile
}
Inc and Value have pointer receivers, so *Basic satisfies Counter
and plain Basic does not. Uncomment that last line and the compiler says
"Basic does not implement Counter (method Inc has pointer receiver)" — one
of the most-Googled Go error messages there is.
Why the asymmetry? Go can always get a value from a pointer, but it can't
always get a pointer from a value — a temporary like Basic{} has no
address to take. Rather than sometimes working, it never does.
The practical consequence: if your methods have pointer receivers, put pointers in your interface slices.
package main
import "fmt"
type Counter interface {
Inc()
Value() int
}
type Basic struct {
Name string
n int
}
func (b *Basic) Inc() { b.n++ }
func (b *Basic) Value() int { return b.n }
func main() {
counters := []Counter{
&Basic{Name: "a"},
&Basic{Name: "b"},
}
counters[0].Inc()
counters[0].Inc()
counters[1].Inc()
for _, c := range counters {
fmt.Println(c.Value())
}
}
[]Counter{&Basic{...}} — the & is doing real work. This is the practical
reason for module 5's consistency rule: pick pointer receivers for a type and
stick to them, and this question only comes up once.
Value receivers work either way
The reverse direction is fine. A type with value receivers satisfies the interface as both a value and a pointer:
package main
import "fmt"
type Greeter interface {
Greet() string
}
type Person struct{ Name string }
func (p Person) Greet() string { return "hi, " + p.Name }
func main() {
var a Greeter = Person{Name: "Ada"}
var b Greeter = &Person{Name: "Grace"}
fmt.Println(a.Greet())
fmt.Println(b.Greet())
}
Go dereferences the pointer for you. So value receivers are the more permissive choice — which is another reason small, immutable types use them.
An interface hides everything else
Once a value is inside an interface, only the interface's methods are reachable:
package main
import "fmt"
type Namer interface {
Name() string
}
type Server struct {
host string
Port int
}
func (s Server) Name() string { return s.host }
func (s Server) Restart() { fmt.Println("restarting", s.host) }
func main() {
var n Namer = Server{host: "api", Port: 8080}
fmt.Println(n.Name())
// n.Restart() // won't compile: Namer has no Restart
// fmt.Println(n.Port) // won't compile: Namer has no fields
}
The Server is still in there, whole and unmodified — but the static type is
Namer, so that's all the compiler will let you touch. Getting back to the
concrete type is a type assertion, which is the next lesson.
Interfaces are types too
Since an interface is a type, it can appear anywhere a type can: as a struct field, a map value, a function result, a channel element.
package main
import "fmt"
type Validator interface {
Validate(string) error
}
type NotEmpty struct{}
type MaxLen struct{ N int }
func (NotEmpty) Validate(s string) error {
if s == "" {
return fmt.Errorf("value is empty")
}
return nil
}
func (m MaxLen) Validate(s string) error {
if len(s) > m.N {
return fmt.Errorf("value is longer than %d characters", m.N)
}
return nil
}
type Field struct {
Name string
Rules []Validator
}
func (f Field) Check(value string) {
for _, r := range f.Rules {
if err := r.Validate(value); err != nil {
fmt.Printf("%s: %v\n", f.Name, err)
return
}
}
fmt.Printf("%s: ok\n", f.Name)
}
func main() {
username := Field{Name: "username", Rules: []Validator{NotEmpty{}, MaxLen{N: 8}}}
username.Check("ada")
username.Check("")
username.Check("verylongusername")
}
Note func (NotEmpty) Validate(...) — a receiver you never use can be
written without a name. And Rules []Validator is a struct field holding
interfaces, so the rules are configurable at runtime and trivially
extensible: a new rule type needs no change to Field.
Your turn
Make *Stack satisfy this Store interface — Put appends, Size reports
the count. The program should print:
2
package main
import "fmt"
type Store interface {
Put(item string)
Size() int
}
type Stack struct {
items []string
}
// add Put and Size with pointer receivers
func main() {
var s Store = &Stack{}
s.Put("a")
s.Put("b")
fmt.Println(s.Size())
}
package main
import "fmt"
type Store interface {
Put(item string)
Size() int
}
type Stack struct {
items []string
}
func (s *Stack) Put(item string) {
s.items = append(s.items, item)
}
func (s *Stack) Size() int {
return len(s.items)
}
func main() {
var s Store = &Stack{}
s.Put("a")
s.Put("b")
fmt.Println(s.Size())
}
Next: getting the concrete type back out of an interface.