Skip to content

Commit 3082328

Browse files
committed
clarification and mnemonic based on feedbac
1 parent ed935d6 commit 3082328

File tree

1 file changed

+28
-5
lines changed

1 file changed

+28
-5
lines changed

content/advent-2019/directional-channels.md

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,26 +36,49 @@ var receiveOnlyChan <-chan string // can read from, but cannot write to or close
3636
var sendOnlyChan chan<- string // cannot read from, but can write to and close()
3737
```
3838

39+
A good way to remember how this works is that, in declarations, the arrow indicates how the channel is allowd to be used:
40+
```
41+
<-chan // data only comes out
42+
chan<- // data only goes in
43+
```
44+
3945
At first glance, this might seem pretty useless --how useful is a new channel if it can't work in both directions?-- but there's another important line in the spec in the very same paragraph:
4046

4147
> A channel may be constrained only to send or only to receive by **assignment** or **explicit conversion**.
4248
43-
This means channels can start out bidirectional, but magically _become_ directional simply by assigning a regular channel to a variable of a constrained type (or passing it into a function with a constrained channel argument, which accomplishes the same thing). This is very useful for creating receive-only channels that no one can close but you.
49+
This means channels can start out bidirectional, but magically _become_ directional simply by assigning a regular channel to a variable of a constrained type. This is very useful for creating receive-only channels that no one can close but you.
4450

4551
## Receive-only Channels
4652

4753
```go
4854
var biDirectional chan string
4955
var readOnly <-chan string
50-
func takesReadonly(c <-chan string){}
5156

5257
biDirectional = make(chan string)
5358

5459
takesReadonly(biDirectional)
5560
readOnly = biDirectional
5661
```
5762

58-
`readOnly` now shares the same underlying channel, as `biDirectional`, but it cannot be written to *or* closed. Most crucially, this distinction is part of its *type*, which means these restrictions can be enforced at *compile time*.
63+
`readOnly` now shares the same underlying channel, as `biDirectional`, but it cannot be written to *or* closed. This can also be done on the way into our out of a function, simply by specifying a direction in the argument or return type:
64+
65+
```go
66+
func takesReadonly(c <-chan string){
67+
// c is now receive-only inside the function and anywhere else it might go from here
68+
}
69+
70+
func returnsReadOnly() <-chan string{
71+
c := make(chan string)
72+
go func(){
73+
// some concurrent work with c
74+
}()
75+
return c
76+
}
77+
readOnly := returnsReadOnly()
78+
79+
```
80+
81+
This is a pretty nifty trick, and works a bit differently to conversions in the rest of the language, but, most crucially, the change in direction is reflected in the *type*, which means these restrictions can be enforced at *compile time*.
5982

6083
```go
6184
go func() {
@@ -75,7 +98,7 @@ fmt.Println(<-readOnly) // "hello" (same underlying channel!)
7598
This is useful not only to control who can write to or close your channel, but also in terms of descriptiveness and Intentionality. One of the nice things about strongly-typed languages like Go is that they can be tremendously descriptive just through their API. Take the following function as an example:
7699

77100
```go
78-
func SliceIterChan(s []int) <-chan int
101+
func SliceIterChan(s []int) <-chan int {}
79102
```
80103

81104
Even without the documentation or implementation, this code unambiguously states that it returns a channel that the consumer is supposed to read from, either forever, or until it's closed (which documentation can help clarify). This lends itself very well to a **for-range** over the provided channel.
@@ -87,7 +110,7 @@ for i := range SliceIterChan(someSlice) {
87110
fmt.Println("channel closed!")
88111
```
89112

90-
Diving into the implementation, the function creates a bidirectional channel for its own use, and then all it needs to do to ensure that it has full control over writing to and closing the channel is to return it, whereupon it will be converted into a receive-only channel automatically.
113+
Diving into the implementation, the function creates a bidirectional channel for its own use, and then all it needs to do to ensure that it has full control over writing to and closing the channel is to return it, whereupon it will be converted into a read-only channel automatically.
91114

92115
```go
93116
// SliceIterChan returns each element of a slice on a channel for concurrent

0 commit comments

Comments
 (0)