Bidirectional Channels
By default, Go channels are bidirectional. A channel declared with a statement like make(chan int)
can be
written to and read from.
ch := make(chan int)
go func() { ch <- 42 }()
val := <-ch
fmt.Printf("%d\n", val)
Creating a bidirectional channel, writing to it and reading from it
Making Channels Send-Only or Receive-Only
Consider the following code:
ch := make(chan int)
foo(ch)
//...
func foo(ch <-chan int) {
// channel can be read from
}
The code creates the channel as above, but in the context of foo()
, the channel is now receive-only. The same thing
could be done in some other function, to make a channel send-only:
func bar(ch chan<- int) {
// channel can be written to
}
In the context of the bar()
function here, the channel can be written to, but it is invalid to read from it.
These checks are done at compile time, so we will see a compilation error if we attempt to read from the channel in a send-only context.
A Frivolous Aside
While many Go developers won’t look at it like this, a tiny fraction of readers might find something pleasing in what is
actually going on here. Parameter type declarations like chan<- interface{}
don’t change the variable at the point of
creation, but only alter its type within the context of the function receiving the channel. The term for this
is type coercion, and it is much more commonly seen in languages with a rich dynamic typing feature set than it is
in statically typed languages like Go.
Summary
This little Easter egg in Go can really help communicate clearly what a channel is for within the scope of a function. You don’t have to use send-only or receive-only type declarations with your parameters, but if you do, it will immediately be clear to the reader what the channel should be used for in this scope. You will also benefit from compile-time errors, should you make a mistake and try to read from a channel you meant to write to, or vice versa.