What I fear myself is feature creep. A design by committee that ruins the language for me, and is never good enough for you.
If not, you might try 0mq, immutable lists or any other facility. Channels are just the in-process idiomatic way to communicate state, but not required at all.
Mutexes go a long way, and underpins channels anyways.
- Needing to determine ownership of closable objects passed across channels.
- Defining what special token (or OOB signaling mechanism) is used for marking end of data.
- Synchronizing who is closing the channel
Go channels are quite painful to use and often a simple lock would do better. This write-up[2] summarizes the issues with them quite nicely:
[1] https://dave.cheney.net/2014/03/19/channel-axioms [2] https://www.jtolio.com/2016/03/go-channels-are-bad-and-you-s...
I disagree that expressiveness lends to feature creep, I've personally never seen that happen, but that's another topic.
For me, the verbosity of Go would be a lot more justified if it aslo had some sort of memory safety system in place (similar to Rust) that could help prevent concurrency related errors. I understand that you're supposed to used channels for everything in Go, but last time I wrote a channel heavy concurrent server the compiler did nothing to prevent me from mutating shared state across different go-routines and I was unable to land on a pure channel based implementation (I needed a run_once to initialize some shared state and a WaitGroup to track active listeners, and this was the case in all the other impls I audited/referenced as well). I just don't feel like I'm getting a lot of value in exchange for the verbosity when writing Go.