0% completed
Nil Channels
On This Page
The Zero Value of a Channel
A Nil Channel by Accident
A Nil Channel Wait in a Running Program
Closing a Nil Channel
Preventing Nil Channels
Key Takeaways
A channel variable that was declared but never given a channel by make holds the value nil. Go defines exactly what each channel operation does on it, and the list is short:
- A send blocks forever.
- A receive blocks forever, and so does a
for rangeloop over the channel. closepanics.lenandcapreturn 0.- A comparison with
nilreportstrue.
The list also contains things Go does not do. There is no compile error, no warning from go vet, and no error at the moment of a send or a receive. A goroutine that sends on a nil channel simply stops at that line and stays there.
That combination makes a nil channel easy to create and hard to notice. The rest of this page answers where nil channels come from, what each operation does on one, and how to keep them out of a program.
The Zero Value of a Channel
Every Go type has a zero value, which is the value a variable holds before anything is assigned to it. For a channel type, the Go specification states that "the value of an uninitialized channel is nil". A nil channel is therefore not a broken channel. It is the absence of a channel, in the same way that a nil map or a nil pointer is the absence of a map or of a value.
Only the built-in make creates a channel. The specification also says that "a nil channel is never ready for communication", and that single sentence explains both blocking rules in the list. A send waits until the channel is ready to accept the value, and a receive waits until it is ready to deliver one. A nil channel never becomes ready, so neither operation ever completes.
The program below checks the operations that return at once. It never sends or receives on the nil channel, because either operation would stop the program.
Output:
nil: true len: 0 cap: 0 nil: false len: 0 cap: 2
A Nil Channel by Accident
Nobody writes a nil channel on purpose and then sends on it. Nil channels appear because a channel field or variable was declared in one place and was supposed to be made in another. The most common case is a struct field. A struct literal that leaves out a field sets it to its zero value, so a channel field left out of the literal is nil.
The type below fetches pages in a background goroutine and delivers them on its pages field. The caller builds it with &Crawler{}, which compiles without complaint.
The program prints no pages. After the first fetch returns, the runtime stops it:
fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan receive (nil chan)]: main.main() goroutine 7 [chan send (nil chan)]: main.(*Crawler).Start.func1()
The state in brackets is the most useful part of the report. A goroutine waiting on a real channel shows [chan receive] or [chan send], as in Unbuffered and Buffered Channels. The words (nil chan) say that this channel was never made, which means the cause is a missing make, not a missing sender or receiver.
Other common sources follow the same pattern. A slice of channels made with make([]chan int, 3) holds three nil channels, because make creates the slice but not the channels inside it. A lookup in a map[string]chan int for a key that is not present also returns a nil channel.
A Nil Channel Wait in a Running Program
The crawler program was stopped by the runtime only because every goroutine was blocked. In most real programs that is not the case. When other goroutines are still running, a goroutine blocked on a nil channel produces no error at all, and the program continues without it.
The program below starts one goroutine that receives from a nil channel. The main goroutine keeps working and then counts the goroutines that still exist.
Output:
goroutines still alive: 2 main finished
The second goroutine is still alive and still waiting when main returns. In a server, a goroutine like that stays blocked until the process ends and holds its stack and every value it refers to. This is a goroutine leak, the same problem described in Waiting for Goroutines with Done Channels, with a cause that is harder to see in the code.
Closing a Nil Channel
Unlike a send or a receive, close on a nil channel does not wait. The specification says that "closing the nil channel also causes a run-time panic", and the program stops at once.
Output:
panic: close of nil channel goroutine 1 [running]: main.main()
The panic is easier to find than the blocking cases, because it happens on the line with the mistake and prints that line in the stack trace.
Preventing Nil Channels
The reliable fix is to make the channel in the same place that creates the value holding it. For a struct with a channel field, that place is a constructor function, which is a plain function that returns a ready-to-use value. The Crawler above becomes correct with one function:
func NewCrawler() *Crawler { return &Crawler{pages: make(chan string)} }
Callers then write c := NewCrawler() instead of &Crawler{}, and the field can never be nil. A slice of channels needs a loop that calls make for each element. For a map of channels, the code that adds a key must store a channel created with make, and readers should use the comma-ok lookup ch, ok := m[key] to tell a missing key from a stored channel.
When a function receives a channel from code it does not control, a check such as if ch == nil at the start can turn a silent wait into a clear error. That check is cheap, and it reports the mistake in the function that received the nil channel instead of at some later receive.
Go does have one deliberate use for nil channels: the select statement never runs a case whose channel is nil, and code uses that on purpose.
| Operation | Nil channel | Made, open channel |
|---|---|---|
ch <- v | Blocks forever | Completes when buffer has room or a receiver is ready |
<-ch | Blocks forever | Completes when a value is available |
close(ch) | Panics | Closes the channel |
len(ch), cap(ch) | 0, 0 | Values in buffer, buffer size |
ch == nil | true | false |
Key Takeaways
- The zero value of a channel type is
nil; onlymakecreates a channel. - A send or receive on a nil channel blocks forever, and a range loop over one never starts its first iteration.
closeon a nil channel panics withclose of nil channel;lenandcapreturn 0.- The runtime reports a nil channel wait only when every goroutine is blocked, and marks it
(nil chan)in the trace. - Make channels in a constructor so that no struct field, slice element, or variable is left nil.
A nil channel is a channel variable that was never assigned a channel from make. Every send and receive on it waits forever, usually without any error. The fix is to create each channel in the same place as the value that holds it.
Reading Progress
0%
On This Page
The Zero Value of a Channel
A Nil Channel by Accident
A Nil Channel Wait in a Running Program
Closing a Nil Channel
Preventing Nil Channels
Key Takeaways