Open ianlancetaylor opened 7 years ago
To be clear I am not proposing that we change the behavior of select statements with nil channels
I like your proposal and would be happy to see it adopted as-is.
That said, I would go a step further and change the behavior of select statements to match. The use-case for selecting on a nil channel is to toggle whether a particular case can be taken, but it's easy enough to do that with a non-nil channel that has no receivers (to block send cases) or no senders (to block receive cases), and I think the benefit of keeping select
and non-select channel ops consistent may outweigh the convenience of nilling out channels to toggle cases.
I think it's good for the statement to be analogous to the select/case except where it needs to be different, and I find blocking on select/case very useful. A lot of times, I will write a loop with a select inside of it, and being able to "turn off" part of the select block with a nil is very useful. See my blog post about this pattern: https://blog.carlmjohnson.net/post/share-memory-by-communicating/
How about panic only when sending to a nil channel? That way we match select and receive behavior and keep the feature of select that it ignores nil channels. I can't give an example right now but I remember it being very useful many times. It's also a common pattern for me to write a loop with select in it that has cases I can toggle on and off.
Also, making select panic would break existing Go 1 code. Not only break but require a complex task of redesigning the application to work around the new behavior. Some cases could be simple but, still, no "go fix" could fix that.
This would effectively remove the possibility of "pausing" a channel as explained in Advanced Concurrency Patterns
In Go 1 a send or receive operation on a nil channel blocks forever. This is by analogy with the select statement, in which a case with a nil channel is ignored. In particular, these four code snippets all block forever:
However, the fact is that send statements and receive expressions are not the same as select cases. They look different in the program and they are implemented differently. While it is nice to have a send or receive operation behave identically to a select statement with a single case that corresponds to that operation, it is not necessary, and I claim that changing that correspondence will not cause confusion.
I claim further that the fact that nil channels block forever is a point of confusion for new Go programmers. They must learn to call
make
to create a channel. When learning Go, it is easy to forget that. This then leads to confusion when programs simply block rather than reporting an error. Sample comments:https://groups.google.com/d/msg/golang-nuts/QltQ0nd9HvE/mAb2K4UHWxUJ https://groups.google.com/d/msg/golang-nuts/JFTymqVDBcs/E8GDJiQFBQAJ http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/index.html#using_nil_ch
My final claim is that essentially no real programs rely on the current behavior for nil channels. I have no way to prove this claim, but in general blocking forever is a goroutine leak, and when people do want to block forever anyhow they use
select {}
.Give the above, I recommend that for Go 2 we change this corner case and make send or receive operations on nil channels panic. To be clear I am not proposing that we change the behavior of select statements with nil channels, and I am not proposing that we change any aspect of the behavior for closed channels. In the above four code snippets, the first and third will panic, while the second and fourth will continue to behave as in Go 1.