golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
123.97k stars 17.67k forks source link

proposal: spec: panic on send/receive on nil channel #21069

Open ianlancetaylor opened 7 years ago

ianlancetaylor commented 7 years ago

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:

var c1 chan int
<-c1

var c2 chan int
select {
case <-c2:
}

var c3 chan int
c3 <- 1

var c4 chan int
select {
case c4 <- 1:
}

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.

bcmills commented 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.

earthboundkid commented 7 years ago

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/

creker commented 7 years ago

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.

toqueteos commented 7 years ago

This would effectively remove the possibility of "pausing" a channel as explained in Advanced Concurrency Patterns