How does a non initialized channel behave?

I have a struct that holds a channel that is not initialized.

When I write into it, the routine is blocking as expected but the reader is never notified that something is in the pipe.

I am surprised that there is no error and I am wondering what is doing Go.

In the example bellow, neither the message pushed nor got it are printed. (Uncomment the intialization and it will work like a charm)

type MyStruct struct {
    over chan bool
}

func main() {
    nonInitialized := &MyStruct{}
    // nonInitialized.over = make(chan bool)
    go func() {
        for i := 0; i < 10; i++ {
            select {
            case <-nonInitialized.over:
                fmt.Println("got it")
            default:
                // proceed
            }
            fmt.Println("do some stuff")
            time.Sleep(10 * time.Millisecond)
        }
        panic("took too long")
    }()
    // push on the non initialized channel
    fmt.Println("pushing")
    nonInitialized.over <- true
    fmt.Println("pushed")
}

Here is the playground https://play.golang.org/p/76zrCuoeoh

(I know I should initialize the channel, this is not the purpose of the question, I want to know what is happening in Go with non initialized channels.)


Solution 1:

An "uninitialized" field or variable of channel type will have the zero value of all channel types which is nil. So let's examine how a nil channel or operations on it behave.

It is worth collecting the channel axioms in one post:

  • a send on a nil channel blocks forever (Spec: Send statements)
  • a receive from a nil channel blocks forever (Spec: Receive operator)
  • a send to a closed channel panics (Spec: Send statements)
  • a receive from a closed channel returns the zero value immediately (Spec: Receive operator)

Reasoning for blocking in case of nil channels: if a channel value is nil, no one has a reference to it, so no one will ever be ready to receive from it (what we want to send); or send anything on it (what we would receive from it).

You can read further reasoning and more details about this in Dave Cheney: Channel Axioms.

For completeness:

  • Closing a nil channel causes a run-time panic (just like closing an already closed channel).
  • Length and capacity of a nil channel is 0; in accordance with nil slices and maps having 0 length and capacity.

Reasoning: "closed" is a state, but a nil channel cannot have a state (there is only one nil channel, and not one for "closed" and one for "not closed" channel). And there are no elements queued in a nil channel (so len = 0), and it does not have a buffer capacity (so cap = 0).

Solution 2:

This is expected behavior. A send to a nil channel blocks forever. You can read about it in the specs here: https://golang.org/ref/spec#Send_statements

The same is also applicable for a recieve on a nil channel. (https://golang.org/ref/spec#Receive_operator)

You can also keep this link handy for reference: http://dave.cheney.net/2014/03/19/channel-axioms