Skip to content

Day 016 — Interfaces & Implicit Satisfaction

Month 1 · Week 3 · ⬅ Day 015 · Day 017 ➡ · Journal index

🎯 Learning Objective

Define and consume interfaces in Go, understand implicit (structural) satisfaction, and avoid the nil-interface trap.

📚 Topics

  • Interfaces as method sets · implicit satisfaction (no implements keyword)
  • "Accept interfaces, return structs" · small interfaces
  • The two-word interface value: (type, value) · nil interface vs nil concrete
  • Empty interface interface{} / any · embedding interfaces

📖 Reading / Sources

📝 Notes

  • An interface is a set of method signatures. A type satisfies it simply by having those methods — no declaration of intent is needed. This is structural/implicit satisfaction → [[interfaces]].
  • This decoupling means you can write an interface in package A that types in package B satisfy without B ever importing A. Define interfaces where they are consumed, not where the concrete type lives.
  • Idiom: "Accept interfaces, return concrete types." Keep interfaces smallio.Reader/io.Writer are one method each and compose everywhere.
  • Whether a type implements an interface depends on method sets → [[value-vs-pointer-receivers]]: if Speak has a pointer receiver, only *Dog implements Speaker, not Dog.
  • An interface value is a two-word pair: (concrete type, value). It is nil only when both are nil. The classic nil-interface trap: returning a *MyError typed nil pointer as an error makes the interface non-nil (type is set), so if err != nil is unexpectedly true → [[nil-interface-trap]].
  • any (alias for interface{}, Go 1.18+) is the empty interface — every type satisfies it. Prefer concrete or small interfaces; reach for any only when you truly accept anything.
  • Interfaces can embed other interfaces: io.ReadWriter embeds Reader and Writer. A compile-time satisfaction check is idiomatic: var _ Speaker = (*Dog)(nil).

💻 Code Examples

type Speaker interface{ Speak() string }

type Dog struct{ Name string }
func (d Dog) Speak() string { return d.Name + " says woof" }

// Compile-time assertion that Dog satisfies Speaker (fails to compile otherwise).
var _ Speaker = Dog{}

func announce(s Speaker) { fmt.Println(s.Speak()) } // accept the interface

func main() {
    announce(Dog{Name: "Rex"}) // Dog never says it "implements" Speaker — it just does
}

Full code: examples/month-01/interfaces/main.go · Run: go run ./examples/month-01/interfaces

🏋️ Exercises / Practice

Exercise Status Link
Shape interface with Rectangle/Circle + TotalArea([]Shape) exercises/month-01/week-3/shapes
Compile-time var _ Shape = ... assertions exercises/month-01/week-3/shapes

🐛 Mistakes Made

  • Returned a (*MyError)(nil) as error and the caller's err != nil fired — the nil-interface trap. Fixed by returning a literal nil.
  • Made an interface with five methods, then could only satisfy it with one struct. Split it into small interfaces.

❓ Open Questions

  • When is it worth an explicit var _ I = (*T)(nil) guard? (Whenever the satisfaction is load-bearing and you want a compile error if it breaks.)

🧠 Active Recall (answer without looking)

  1. Q: How does a Go type declare that it implements an interface?

    A It doesn't — satisfaction is *implicit*. Having the required method set is enough; there is no `implements` keyword.

  2. Q: Why can an error value be non-nil even though it "holds nil"?

    A An interface is a (type, value) pair and is nil only if *both* are nil. A typed nil pointer (e.g. `(*MyError)(nil)`) stored in an `error` sets the type word, so the interface is non-nil — the nil-interface trap.

🪶 Feynman Reflection

An interface is a contract written as a checklist of methods. In Go nobody signs the contract — if your type happens to have every method on the list, it is a valid party to the contract automatically. That looseness is what lets unrelated packages fit together like Lego without importing each other.

🕳️ Knowledge Gaps

  • Reflexes around the nil-interface trap in real error-returning code — drill it in the errors days.

✅ Summary

I can define small interfaces, rely on implicit satisfaction to decouple packages, and I know an interface value is a (type, value) pair — which explains the nil-interface trap.

⏭️ Next Steps / Prep for Tomorrow

  • Day 017: getting the concrete value back out with type assertions and type switches.

Time spent Difficulty Confidence
90 min 🟦🟦⬜⬜⬜ 🟦🟦🟦⬜⬜

Suggested commit: feat(examples): interfaces and implicit satisfaction (day 016)