Day 059 — Channel axioms (close, nil, direction)¶
Month 3 · Week 1 · ⬅ Day 058 · Day 060 ➡ · Journal index
🎯 Learning Objective¶
Internalise the precise rules for sending, receiving, closing, and nil channels so concurrency code never panics or hangs by surprise.
📚 Topics¶
- The close axioms; the nil-channel rules; directional channel types (
chan<-,<-chan) - Who owns a channel and who closes it
📖 Reading / Sources¶
📝 Notes¶
- Close axioms → [[channel-axioms]]:
- Send on a closed channel → panic.
- Close a closed channel → panic.
- Close a nil channel → panic.
- Receive from a closed channel → returns immediately with the zero value,
ok == false(forever). - Rule of thumb: the sender closes, never the receiver, and only one goroutine should own closing. With multiple senders, coordinate close elsewhere (a separate "done" signal or
sync.Once), never let a sender close. - Nil channel rules → [[nil-channel]]:
- Send to nil → blocks forever.
- Receive from nil → blocks forever.
- This is useful: setting a channel variable to
nildisables itscasein aselect, a clean way to "turn off" a branch. - Directional types document and enforce intent → [[directional-channels]]:
chan<- Tsend-only,<-chan Treceive-only.- A bidirectional
chan Tconverts implicitly to either direction (not back). Functions should accept the narrowest direction they need — e.g. a generator returns<-chan T. - Closing is a broadcast: every receiver and every
rangeobserves it. That's why a closeddonechannel is the classic cancellation fan-out.
💻 Code Examples¶
// A nil channel disables its select case — toggle a branch off at runtime.
var in <-chan int = produce()
for {
select {
case v, ok := <-in:
if !ok {
in = nil // closed: stop selecting this case, never panic-receiving
continue
}
use(v)
default:
return
}
}
(No standalone example — this builds on
examples/month-03/channels; the nil-case trick recurs in Day 060/062.)
🏋️ Exercises / Practice¶
| Exercise | Status | Link |
|---|---|---|
Fan-in Merge that closes the output exactly once |
✅ | exercises/month-03/week-1/fanin/ |
🐛 Mistakes Made¶
- Two sender goroutines each
closed the shared channel → second close panicked. - Left a channel
nilby forgettingmake; the goroutine blocked forever instead of erroring.
❓ Open Questions¶
- Best pattern to close when there are N senders? (Common answer: a separate "done" channel + a
WaitGroupcloser goroutine, as in the fan-in exercise.)
🧠 Active Recall (answer without looking)¶
-
Q: What happens if you send on a closed channel? On a nil channel?
A
Closed → panic. Nil → blocks forever (the receive on nil also blocks forever). -
Q: Why is
chan<- Tin a function signature a good idea?
A
It restricts the function to send-only, documenting intent and letting the compiler catch an accidental receive or close.
🪶 Feynman Reflection¶
A closed channel is like an empty vending machine that's been switched to "free dispense forever": you always get the zero item instantly, but trying to restock (send) or switch it off again (re-close) breaks it (panic). A nil channel is an unplugged machine — press any button and you wait forever, which is occasionally exactly what you want in a select.
🕳️ Knowledge Gaps¶
- Idiomatic multi-sender close coordination — practise more in the fan-in exercise.
✅ Summary¶
I know the four panic cases around close, the block-forever behaviour of nil channels (and its use in select), and why directional types belong in signatures.
⏭️ Next Steps / Prep for Tomorrow¶
- Day 060:
selectto wait on many channels, andtime.Afterfor timeouts.
| Time spent | Difficulty | Confidence |
|---|---|---|
| 90 min | 🟦🟦⬜⬜⬜ | 🟦🟦🟦⬜⬜ |
Suggested commit: docs(journal): channel axioms — close, nil, direction (day 059)