while (!CspChan_closed(chan)) { ... } seems like a concurrency footgun – what's stopping the channel from being closed in between the check for it being closed and the operation on the channel?
Nothing stops that. You'll end up getting a message full of 0 bytes (messages are fixed size) if it closes between the `closed()` check and the `receive()` operation. Common designs in this space instead indicate if the receive call has obtained data (return values typically indicate this). This is a kind of time-of-check/time-of-use bug.
Go, for its part, has its receive operation, `<-`, return a status indicating if the channel is still open, as in `v, ok := <-ch`
I don't think CspChan_closed is a super useful operation if using it is always prone to TOCTTOU issues – maybe as an optimization hint but that's about it. In Go, the receive operation indicating the channel is closed is atomic with receiving a value, so it's not possible to have a TOCTTOU problem there.