ADR 0024: Channel operations are methods; `select` has `when`-style branches
Status: Accepted; channel types respelled chan T, chan<- T, <-chan T, and select bindings v := ch.receive(), by 0031
Context
Section titled “Context”Channels were not implemented yet. Go spells their operations with the
<- operator: ch <- v sends, <-ch receives, and close(ch) closes. The
operator is the least readable part of Go’s syntax for programmers new to Go,
and it is a lexing hazard in Lyth, where x<-1 would mean a send rather than
the comparison x < -1. Kotlin’s coroutine channels use send and
receive, which Kotlin programmers already know, and Go channel types have no
methods that these names could collide with.
Decision
Section titled “Decision”-
Channel types are
Chan<T>,SendChan<T>(Gochan<- T), andReceiveChan<T>(Go<-chan T; previously spelledRecvChan<T>). Channels are created withmake(Chan<T>)ormake(Chan<T>, n), as slices and maps are. -
Operations are methods that lower to Go’s operators with no added cost:
Lyth Go ch.send(v)ch <- vch.receive()<-chval (v, ok) = ch.receive()v, ok := <-chch.close()close(ch)sendis a statement; it has no value.sendandcloseneed aChan<T>orSendChan<T>,receiveaChan<T>orReceiveChan<T>.for (v in ch),len(ch), andcap(ch)are unchanged. -
receive()returnsT?whenThas no zero value (a non-null pointer, interface, function, or channel), since a closed channel yields nil. This is the rule map lookups already follow, and it gives the idiomval job = jobs.receive() ?: break. -
goanddefertake a function call; a send or receive is not one, sogo ch.send(v)is E0216 with the hintgo { ch.send(v) }. Go has the same rule; wrapping in a closure would silently move the evaluation ofvinto the goroutine. -
selectwaits on several operations. Each branch is one channel operation followed by->and a body, as inwhen;elseis Go’sdefault:select {val job = jobs.receive() -> handle(job)val (r, ok) = results.receive() -> if (!ok) returnout.send(next) -> sent++ctx.Done().receive() -> returnelse -> idle()}As in
when,breakinside aselectbranch exits the enclosing loop, not theselect.selectis a statement, not an expression: branches usually return, send, or loop, and a value form would need anelsethat a blockingselectdoes not have. -
Semantics are Go’s: an unbuffered send waits for a receiver, receiving from a closed channel returns the zero value (
okis false), sending on a closed channel panics, and a nil channel blocks forever. Kotlin’s channels throw on closed channels instead; the guide documents the difference.
Alternatives
Section titled “Alternatives”- Go’s operators: zero translation from Go code, but the syntax new users find hardest to read, and a lexing trap next to comparisons.
- Constructor syntax
Chan<T>(n): reads naturally, but adds a second way to make a channel next tomake, which slices and maps also use. - Kotlin’s
select { ch.onReceive { v -> } }: needs receiver lambdas and builder semantics that Lyth rejected (ADR 0012).
Consequences
Section titled “Consequences”Translating Go examples means rewriting <- (<-ctx.Done() becomes
ctx.Done().receive()). The names cannot collide with a Go type’s methods:
Lyth only calls exported Go methods, and these are lower-case. The checker
treats the three operations as built-in members of channel types (named Go
channel types included), like the prelude methods on strings. select
became a keyword.