Direction specifiers on channels are a form of type constraint that seems rarely used (TODO: provide data). While it may be useful documentation, in reality it is hard to enforce: If a channel is marked as a read- or write-only channel, it may still be possible to send or receive from it through an unrestricted alias that refers to the same channel (which cannot easily be prevented statically at compile time, in general). A channel directions is a type constraint, not a runtime property.
Creating a directed channel with make
doesn't make much sense either: It could only be used to send or receive from it which would render it virtually useless as it cannot be assigned to an unrestricted channel variable. (One could use an unsafe type conversion, but that's not the point.)
Thus, besides documentation, directed channel types serve mainly as a restriction on functions operating on them. But it's unclear that the restriction prevents a significant source of bugs: A function that sends rather than receives (or vice versa) from a channel is likely to cause immediate trouble (deadlock) upon its first wrong channel operation; such a function is unlikely to survive a basic test case.
Specifying a channel direction may be most useful in package interfaces, but there is a growing consensus that generally channels shouldn't be passed across significant API boundaries.
Channel directions are unusual in the Go type system in the sense that there are no similar constraints on other types: Go doesn't have have a read-only or write-only attribute on any other type or variable; which again may be very useful across API boundaries. Instead, we cope with providing accessors (functions/methods) instead, to prevent undesired reads or writes. One could use the same mechanism for channels.
Finally (and perhaps least importantly), the syntax for channel directions is a source of ambiguity when parsing conversion expressions (https://tip.golang.org/ref/spec#Conversions) which requires incommensurable extra work during parsing.
In summary, I propose that we remove the ability to specify channel directions for Go 2.
This would not be a backward-compatible change. In a first step, one might simply ignore the direction specification. In a second step, one might remove the ability to specify the direction altogether. If we decide on a mechanism (annotation in source) for selecting the accepted language at compile time, we could change the syntax more readily.
Finally, there's the issue of reflection: The reflection API allows to inspect a channel direction, and to create directed channel types. For Go 2 channels, that direction would always be unrestricted. For backward compatibility, reflect may need to continue supporting directions. Perhaps the restrictions can me made no-ops.
I know I'm in the minority amongst the Go team on this one, but I really like type restrictions. I agree that it's weird that channels can be restricted and nothing else can, but my personal preference is to let more things be restricted instead. Again, I know y'all hate that.
But I would be sad to see this enforced documentation go away.
Channel directions are unusual in the Go type system in the sense that there are no similar constraints on other types: Go doesn't have have a read-only or write-only attribute on any other type or variable; which again may be very useful across API boundaries. Instead, we cope with providing accessors (functions/methods) instead, to prevent undesired reads or writes. One could use the same mechanism for channels.
You could't use accessors with select
unless it returned the channel which defeats the purpose
It would be very unfortunate to write wrapper methods for every API that returns a read-only channel. I don't really care about the performance difference of having 50,000 goroutines calling foo.Next() *Item
vs foo.Chan() <-chan *Item
once, but not having the ability to select
on foo.Next() *Item
just kills a major advantage of Go for a lot of pub/sub systems currently in production.
In the chain of concurrent language designs that led to Go, this was the missing piece. Go is the first language in the sequence that has them, and it was an important piece of the puzzle. Like @bradfitz, I would be very sad to see them disappear.
The fact that they are used rarely is not a design problem but an education one.
Also, I disagree with the blanket disdain for channels crossing API boundaries. Not every program is a web server
In any case, returning a channel from a function is a common pattern even if returning one from a package is not. Being able to express the channel direction in a function call or return adds type safety. Why remove that ability?
@jimmyfrasche , @as Of course, we wouldn't want to write wrapper methods for all read-only, write-only channels; and I'm aware that select
would be a problem (for accessors). Similarly, we wouldn't want to write accessors for all variables if the the language had a mechanism to specify a read-only attribute for any variable and now it were proposed that it be removed.
The point I am trying to make is that while channel directions may be useful annotations, they don't carry their weight in extra complexity. There is quite a bit of extra prose required to explain channel directions, code required to parse them correctly (due to the ambiguity), and a little bit to handle them during type-checking. Yet after that, they have zero semantic effect, except that the information has to be carried along into reflection info. Just because it is useful, doesn't mean it's important to have.
@robpike I am unsure as to which part of the puzzle you are referring to; but I am probably forgetting something.
As we are thinking about changes for Go 2, I do see a lot of additions but little in the way of cleaning up what we have. In my mind this is a place where we can simplify and make the language more regular in a meaningful way with virtually zero impact on its power and usability.
Let's make no mistake: If we are not willing to streamline and remove things, we are committing to a path of steady additions to the language. Ultimately this is the fate to which most other languages succumbed to. (There may be better places to simplify, and this is a proposal only.)
Thus, I would like to see a concrete argument as to why being able to specify the direction of a channel is crucially important. And if it is, shouldn't there be a similar constraint (read/write access) for any other type? (Or perhaps a general mechanism that unifies and thus simplifies?).
@griesemer, one possible compromise is that channel directions could exist only syntactically as documentation but not be part of the type system or reflect info, and leave it up to vet (or cmd/compile, maybe) to check violations of.
It'd be somewhat akin to struct tags which are in the language only barely but mostly enforced outside of the language.
seems rarely used
This is weird. When I write concurrent Go code, pretty much every channel variable has a direction constraint.
And that makes sense. There is not a lot of situations (if there are even any) when one goroutine needs to send and receive on the same channel. It seems to me that in pretty much every sound concurrent system, one goroutine either sends or receives on one channel, but never both.
Considering this, direction constraint on channels makes huge sense, as it should be used almost always and makes it clear where a goroutine is located in the net of channels.
Its easier to explain a compile time error than debug an improper use of a channel meant to be read-only. In my experience, Go is easy to work with because it takes away the option to do the wrong thing. New users from other languages, specifically from ones with naive concurrency primitives, do the wrong thing.
Go doesn't exist in a vacuum of open source projects. I can count several production systems that return receive only channels and ensure that new programmers do not do crazy things with them. The guarantee that a package importer can't interrupt a message pump by closing or writing to a channel is very nice.
Maybe the annotation will become important for me in future concurrent programs, but from my experience I鈥檇 be ok with removing channel direction annotation.
Function naming documents expected channel uses, the channel send and receive syntax is already resilient to easy mistakes, figuring out the syntax takes more effort than most constructs, and channel direction seems easy to reason about.
Direction specifiers on channels are a form of type constraint that seems rarely used
I also must disagree here. Direction constraints are an important bit of context that significantly aid code comprehension. I use them at every opportunity, and I would be flabbergasted to see this valuable feature removed. I echo @bradfitz and wish constraints like this were applicable to more types, not fewer (e.g. const
).
I'm also one of the people who use channel directions whenever a channel is passed outside a single function. And it is a thing I learned from stepping into that particular piece of lego one too many times. Yes, the deadlocks were caught by simple testing, but they did take a while to figure out and if I had used channel directions from the get-go, they would've been more obvious and flaws in the model would have been immediately clear.
Personally, I'd much rather remove undirected channel types, than directed ones.
Creating a directed channel with make doesn't make much sense either: It could only be used to send or receive from it which would render it virtually useless as it cannot be assigned to an unrestricted channel variable.
Alternatively, make
could return two channels, one for reading and one for writing. If we want a backwards-compatible change, we could make that an alternative form (akin to ,ok
) or use a separate identifier, if we are unhappy about the continual overloading of make
(or think this doesn't fit the bill of calling something make).
Channel directions are unusual in the Go type system in the sense that there are no similar constraints on other types: Go doesn't have have a read-only or write-only attribute on any other type or variable
Unlike other types (that are "just" data containers), channels do have an inherent direction property, as they are designed for transporting values from a sender to a receiver. Sending data the wrong direction is a logical error, and I'd rather have the error caught for me at compile time than during tests that have to be written to take effect.
(Edited to add:) I do agree on the "hard to enforce" part.
I didn't even know you could have bidirectional channels, that's how often I've wanted them.
I feel the 3d, 4th and 5th paragraphs are more arguments for a "no publicly visible channels" proposal, which I'd personally not propound but appreciate the merits of.
The first paragraph bothers me in the same way I was bothered by the proposal to remove the complex types; how many lines of public user code are required to justify something's existence?
As make
-ing directed channels has limited utility I'd suggest prohibiting same in Go2, but it's disconcerting to think of what could happen if the direction "documentation" wasn't enforced if channels are allowed in public.
I like the feature, but I also think it's one extremely few people would have missed if it were never introduced to begin with. I would be a fan of removing it if it helps to offset the language complexity of the higher demand features I hope to see in Go 2. You have my 馃憤 for that reason.
I can't speak to language tooling complexity, but channel directionality made immediate sense to me, and I decorate my channels at every opportunity to reflect their intended usage, esp. in the context of the adage, _Do not communicate by sharing memory; instead, share memory by communicating._
Wait, so is
c := make(chan<- int)
currently legal? If so, why? Directionality constraints are strictly for the purpose of restricting what a consumer of an API can do with a channel; I would imagine the creator of the channel still wants to be able to use the channel unconstrained. If anything, we should at least get rid of the ability to make directioned channels in calls to make
.
And I don't understand the arguments against exposing channels. Why is that a bad thing anyway, and why particularly for web servers?
@andlabs One could actually use a send-only channel, created via make(chan<- T)
(for some suitable T
) without ever needing to read from it, as this slightly convoluted example shows: https://play.golang.org/p/nAa9q4AsUX6 .
@andlabs
send-only channel can be used as a counter by calling len(aSendOnlyChannel)
, though the counter has a ceiling limit.
I'm going to retract this proposal; clearly there's no support for this idea.
Having checked the original CSP paper, I find this in the proposals:
(4) Such communication occurs when one process names another as destination for output and the second process names the first as source for input. In this case, the value to be output is copied from the first process to the second. There is no automatic buffeting: In general, an input or output command is delayed until the other process is ready with the corresponding output or input. Such delay is invisible to the delayed process.
I also checked "Communicating Sequential Processes" (the book) by C.A.R. Hoare, author of the original CSP proposal. Chapter 4, which introduces channels, states:
We shall observe the convention that channels are used for communication in only one direction and between only two processes.
So it looks to me as if it was only ever intended for CSP channels to be unidirectional. Which doesn't in itself mean Go shouldn't have bidirectional channels, of course, but would probably explain why people with a CS background find directional channels natural.
Also of note, Rust channels are deliberately unidirectional. I'd also guess that CSP correctness verification is broken by bidirectional channels, but I have no proof to hand.
Much later ...
The book "Concurrency in Go" (Katherine Cox-Buday), which makes and demonstrates good use of channels as interfaces, uses unidirectional channels extensively to establish ownership of a channel, typically encapsulating the write site lexically. Although I'd initially thought much like Griesemer that the channel direction annotation in Go wasn't that significant in programming practice, I changed my mind completely.
Most helpful comment
I know I'm in the minority amongst the Go team on this one, but I really like type restrictions. I agree that it's weird that channels can be restricted and nothing else can, but my personal preference is to let more things be restricted instead. Again, I know y'all hate that.
But I would be sad to see this enforced documentation go away.