If the language is powerful enough in the expressiveness it offers for building libraries that provide the same features as builtin types, that is also achievable.
This is the path being trailed by the languages more expressive than Go.
I don't agree. Having an event loop built in to the core of the language is a major advantage. Look at Python and async io: Twisted has been around for over 15 years now. asyncore even longer. Tornado exists because apparently Twisted and asyncore weren't good enough. I'm not even sure I know how many incompatible "coroutine" libraries are built on top of generators.
node.js may have a language and standard library that isn't nearly as nice as Python, but it has a built-in event loop. This sidesteps an entire universe of problems. Unfortunately node still has another, similar problem: Does the library use callbacks? Promises? Generator-based coroutines? async/await? If it doesn't use the same style as what your codebase currently uses, you're going to spend a lot of time converting between continuation styles.
Languages like Go and Erlang simply don't have any problems like this.
Now, one might argue that leaving it up to libraries allows greater exploration of the problem space and that blessing a concurrency method stifles innovation, and that might be right. But having a consistent concurrency story built right in to the language is a large advantage.
Totally agree. A inbuilt story for concurrency helps to create a solid and mostly unified ecosystem around the language, especially around anything that is IO related.
The contrast to these are C++ systems, where nearly each application and framework has it's own eventloop integration (and/or coroutine implementations, thread models, futures, ...). This means integration between various libraries gets super hard and code reuse isn't that big.
The only language I can think of which isn't super opinionated but get's now halfway standardized APIs is C#, due to the standardization on Task<T> APIs and async/await.
It will get pretty interesting what the future will bring for Rust and Swift, as these are not opinionated either.
I was getting ready to post the exact same thing. The built in event loop is major. Go did a lot of modelling from Erlang, but made tradeoffs that kept it closer to a C-style programming model.
It's a great language and the limits of its concurrency model aren't something that will really be apparent to you if you haven't learned Erlang/Elixir. For most people, it provides a concurrency model that's a cut above everything else out there.
The main differences in Go and BEAM languages (Erlang/Elixir) are cooperative vs prescheduling, shared memory vs immutable message passing, general garbage collection vs heap per process, and lastly general runtime vs isolation.
Cooperative scheduling means that the scheduler has to have control relinquished back to it (such as with I/O events), while prescheduling will take back control. Cooperative means that you can get better end-to-end performance for a benchmark but you run the risk of a code taking over the processor. Prescheduling will allow consistent performance for all operations in the run time without allowing anything to overtake it. That's one of the ways that it's possible to reliably run a database within the runtime alongside the rest of your code on BEAM.
Shared memory with pointers is pretty standard and definitely provides some performance perks, especially when dealing with large data structures. The flip side means that native clustering doesn't work. With BEAM languages that lack shared memory and rely on message passing, you can just as easily call a function on a different server in another data center as you can a function in your current heap space. This makes it possible to smoothly distribute everything without having to worry about updating shared memory on a specific machine or having the state get out of sync. The channels model helps to avoid this, but by even including shared memory in the language you make the trade off of losing natural clustering for distribution.
With shared memory comes garbage collection and GC pauses, although the Go team has done great work optimizing this. With BEAM every new process (equivalent to a go routine) gets it's own heap meaning that it can be independently cleaned up without pausing the entire stack. This also makes hot deployments possible, so doing things like deploying an update to a codebase with millions of active websocket connections can be done without trigger millions of simultaneous re-connections.
The general runtime vs isolation means that a goroutine that blows up and crash the entire system if it's not properly handled at the point of the problem. When writing Go code you find yourself writing a line of error handling for every line of functionality. With BEAM isolation, processes are kicked off with id's and the processes are so inexpensive that the standard method is to create two - one as a supervisor and one as the process. If the process ever crashes for some reason, the supervisor just restarts it immediately. This creates a granular level of isolation and reliability. There is a library for Go that I remember seeing that seems to create a supervisor pattern for reliability though (http://www.jerf.org/iri/post/2930).
Go will win benchmarks because of it the choices the language made, but the benefits for long term run time, reliability, distribution and performance consistency in the face of bad actors will be in favor of Erlang/Elixir.
That said, the steps that Go took toward implementing the closest thing to Erlang-like concurrency makes it the winner by far among the non-BEAM languages.
This is the path being trailed by the languages more expressive than Go.
That path was the default prior to Go. Go's built-in concurrency, with the predecessor work by Rob Pike and others, was "the breath of fresh air" when it popularized its particular approach to concurrency several years ago.
If the language is powerful enough in the expressiveness
...it can create problems when programming "in the large." The challenge with programming languages has never been "expressiveness" in isolation. Few languages are more expressive than Lisp, and that's been around for a very long time. The problems have always had to do with various tradeoffs with performance and programmer effort in different contexts.
If the language is powerful enough in the expressiveness it offers for building libraries that provide the same features as builtin types, that is also achievable.
This is the path being trailed by the languages more expressive than Go.