Receive operator

February 23, 2024

Reminder! On Monday’s livestream, we’ll be Pair Programming on a real project. Don’t miss it!


Channels are deceptively simple. They seem a bit magical. They feel like they do something. They feel like they send data around your system. But they really don’t. They’re just data types. They’re probably best thought of like Go’s array or slice type, with the limitation that you can only ever read the first value, and you can only ever set the last value. But let’s look at the details. We’ll start where the spec starts, with reading from a channel:

Receive operator

For an operand ch whose core type is a channel, the value of the receive operation <-ch is the value received from the channel ch. The channel direction must permit receive operations, and the type of the receive operation is the element type of the channel.

You may recall that channel types include a concept of directionality:

chan T          // can be used to send and receive values of type T
chan<- float64  // can only be used to send float64s
<-chan int      // can only be used to receive ints

So the receive operator only works on channels that can be used to receive. That means either bidirectional (i.e. chan T) or receive-only (i.e. <-chan T).

And just as with arrays or slices, when you read a value from a channel, the result is the same type as the element of the channel.

var x <-chan int

y := <-x // y is of type int

… The expression blocks until a value is available.

This is one of the ways in which channels are distinct from arrays and slices. When you try to read from a channel, and there’s nothing there, the program blocks! This may sound a bit confusing at first, bit it’s really why channels are so powerful. It’s what allows us to use channels to synchronize different parts of our programs.

(Note: There is a way to not block when trying to read from a channel with no data—we’ll cover that when we get to select statements).

… Receiving from a nil channel blocks forever.

I think this follows from the previous statement. A nil channel has no data available “yet”… so of course it blocks.

… A receive operation on a closed channel can always proceed immediately, yielding the element type’s zero value after any previously sent values have been received.

There’s not a good analogy between a closed channel and an array or slice. When you read from a closed channel, you always immediately get a response. But that response is the zero value.

How do you know if that value was the zero value because it was closed, or because the sender sent the zero value? We get to that in just a moment.

First, some examples from the spec:

v1 := <-ch
v2 = <-ch
f(<-ch)
<-strobe  // wait until clock pulse and discard received value

A receive expression used in an assignment statement or initialization of the special form

x, ok = <-ch
x, ok := <-ch
var x, ok = <-ch
var x, ok T = <-ch

yields an additional untyped boolean result reporting whether the communication succeeded. The value of ok is true if the value received was delivered by a successful send operation to the channel, or false if it is a zero value generated because the channel is closed and empty.

So that’s how you can tell the difference between a sent zero value, and one received because the channel was closed!

Quotes from The Go Programming Language Specification Language version go1.22 (Feb 6, 2024)


Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Get daily content like this in your inbox!

Subscribe