In general most Go apps that have been deployed are Web apps and server infrastructure, where concurrent garbage collection is not too much of a problem in practice. So Go's choice makes sense in Go's context. It does limit parallel scalability in some contexts—which of course are not the contexts that most people have been using Go for at this point.
> What's also true and potentially compensatory is the C-like degree of control Golang gives you over how you allocate memory and lay it out.
Go doesn't give you C-like control over allocation of memory. Language constructs will allocate memory in ways that are not immediately obvious, to quote Ian Lance Taylor . It does give you control over layout of memory.
> I'm not sure the unbounded channel thing is a real advantage for Erlang. I'm happy to be convinced I'm wrong. What's a real, correct design which would be hard to realize in Golang (without unbounded channels) that relies on unbounded channels?
Suppose you're pulling down images from the network and printing out a sorted list of URLs of all the images you find. You might structure it as two goroutines A and B. Make two channels, "urls" and "done". Goroutine A is the network goroutine and simply crawls looking for images to stream to B over the channel "urls". When it's done it sends "true" on "done". Goroutine B is the sorting goroutine and first blocks on the channel "done" before it proceeds, after which it drains the "urls" channel and sorts the results.
This program contains a deadlock due to synchronous message sends. If there are more URLs to be downloaded than the buffer size of "urls", then the program will deadlock. If "urls" were an asynchronous channel, however, this would be fine.
Of course this can be structured to fix it, by doing the send in another goroutine for example (although that costs performance). But hopefully that's a good illustration of the subtleties of synchronous message sending.
These are two sides of the same coin. Having control over memory layout allows you to implement what are in effect allocators.
(Also, if you implement memory pools, the GC still has to trace the pointers within at mark time.)