Lesson 6 / 25
Directional Channel Types in APIs
Let the compiler enforce who sends and who receives.
chan<- and <-chan
A channel type can be restricted: chan<- T is send-only and <-chan T is receive-only. A bidirectional chan T converts implicitly to either, but not back. Use them in function signatures to document and enforce roles: a producer takes chan<- T or returns <-chan T, a consumer takes <-chan T. The compiler then rejects a consumer that tries to send or to close (closing a receive-only channel is a compile error), which encodes the "only the sender closes" rule in the type system. A common idiom is a generator that creates the channel, starts a goroutine to fill and close it, and returns it as <-chan T.
A generator returning a receive-only channel
The function owns the channel; callers can only receive.
package main
import "fmt"
// count owns out: it creates it, sends on it and closes it.
func count(n int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for i := range n { // Go 1.22+: range over an int
out <- i
}
}()
return out
}
func printAll(in <-chan int) {
for v := range in {
fmt.Println(v)
}
// in <- 1 // compile error: send to receive-only channel
// close(in) // compile error: cannot close receive-only channel
}
func main() {
printAll(count(3))
}Return <-chan, accept the narrowest type
Exported functions should rarely accept or return a plain chan T. Narrow types make ownership obvious in code review and prevent accidental double closes.
Quick check: What happens if code calls close on a value of type <-chan int?
- It is silently ignored
- It panics at run time
- It closes the channel normally
- The program fails to compile
Answer
The program fails to compile — Closing a receive-only channel is rejected by the compiler.