> why does closing a channel multiple times panic, but reading from a closed channel returns a zero value?
Not only that, but writing to a closed channel also panics. You need to close it exactly once from the sender side, and then somehow differentiate on the receiving side between an explicit zero value being sent, the channel being empty but not closed, and the channel being empty but not closed. It's not clear to me how this is possible without either using another channel (and then basically repeat the same problem on that new channel) or use some sort of shared memory like an atomic bool, at which you're no longer purely message passing.
I don't have any qualms with shared atomic primitives for synchronizing concurrency, but it's kind of weird that everyone talks so much about goroutines and channels when channels have such a weird design. Needing to use a separate mechanism to circumvent completely avoidable design issues for anything more complex than "never close the channel" does not seem particularly praiseworthy to me.