Skip to content

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 nil disables its case in a select, a clean way to "turn off" a branch.
  • Directional types document and enforce intent → [[directional-channels]]:
  • chan<- T send-only, <-chan T receive-only.
  • A bidirectional chan T converts 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 range observes it. That's why a closed done channel 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 nil by forgetting make; 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 WaitGroup closer goroutine, as in the fan-in exercise.)

🧠 Active Recall (answer without looking)

  1. 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).

  2. Q: Why is chan<- T in 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: select to wait on many channels, and time.After for timeouts.

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

Suggested commit: docs(journal): channel axioms — close, nil, direction (day 059)