Here are some potential reasons (I obviously don't know the actual reasons). You won't like any of them, but I hope you can take me at my word that I'm not a partisan (I have a passing familiarity with Erlang that comes of maintaining a Riak cluster, and I've built some fairly large projects in Golang, but my core languages are C and Ruby).
* Golang has a more conventional, familiar, boring, "safe" language design than Erlang does. If your current language is Python, you're apt to find Golang congenial; I describe Golang to my friends as "a modernized hybrid of Python and Java".
* Golang has a more flexible concurrency offering than Erlang. It has a very flexible (statically typed, sync-or-async) first-class channel type that you can deploy anywhere in a program whether you parallelize it or not. More importantly, it has first-class support for "conventional" shared-everything concurrency with mutexes and semaphores and whatnot. If you're designing everything from scratch to fit your language's preferred idiom, this may not matter, but if you're porting an existing design it might matter a great deal.
* Golang has what I will perhaps hyperbolically refer to as first-class support for strings, with reasonably performant regular expressions, a clearly delineated "just a bag of bytes" type designed from scratch for high-performance buffering and packet framing/demux, and a sane approach to UTF-8 Unicode. It also turns out to be a very elegant framework (as imperative languages go) for building parsers, since you can put the concurrency stuff to work for it. These are common programming tasks and not one Erlang is well known for handling gracefully.
* As I understand it, both Erlang and Golang have comparable lightweight process/thread/coroutine/whatever facilities. Golang was designed from the start to handle huge numbers of demand-spawned threads with small, dynamically allocated stacks. I'm not saying Golang does this better than Erlang does, but it might be tricky to argue that Erlang's process model is better than Golang's.
* Erlang requires an installed runtime and executes in a VM. Golang produces native binaries. You can compile a Golang program on one machine, rsync that single binary to another machine that has never been blessed with a Golang installation of any sort, and run it without a problem.
* Golang has modern, extremely high-quality tooling. Entire huge Golang projects rebuild in seconds or less. The compiler is extremely helpful. Code formatting and documentation are first class problems for the toolchain. The Go package system is well thought out, and, again, is familiar to Python programmers.
> Golang has a more conventional, familiar, boring, "safe" language design than Erlang does.
Do you mean "safe" as in "nobody got fired for buying IBM" or as in language making hard to start thermonuclear war by accident? Because Erlang, with it's immutability everywhere is as safe as, or safer than Go.
> Golang has a more flexible concurrency offering than Erlang.
Basically everything you wrote here Erlang has. And what they were porting was in Python + GEvent, which means no threads anyway.
> a clearly delineated "just a bag of bytes" type designed from scratch for high-performance buffering
Erlang's binaries and strings (two different types) are designed for the same thing.
> but it might be tricky to argue that Erlang's process model is better than Golang's
Well, Go did a nice job borrowing Erlang's solution here. As it origins from Erlang, it has to be good. ;)
> Erlang requires an installed runtime and executes in a VM. Golang produces native binaries.
Erlang is able to produce a single binary. That it contains a VM and libraries and user code is another matter, but rsyncing Erlang binaries is also possible.
> Golang has modern, extremely high-quality tooling.
Dialyzer, EDoc, various process viewers, debuggers and so on for Erlang are really mature.
I think that familiarity of syntax, familiar programming paradigm and raw (single threaded, number crunching) speed were the reasons. Go has many benefits over Erlang in the realm of marketing, but I don't believe Erlang is much worse a language because of it.
I meant "safe" in the "nobody got fired for buying IBM" sense. My sense of it is that from now until the heat death of the universe, Golang developers will say that language is safer because of its type system, and Erland developers will say that language is safer because of share-nothing and immutability. I think both languages are safer than Python.
But then, you cannot have it both ways. Either Erlang is "safer" because of share-nothing and immutability, or Golang is more flexible w/r/t concurrency. I can design a shared concurrent data structure in Golang that is protected by locks or atomic counters, and I can do that easily, using the native first-class facilities of the language. Can two Erlang processes cooperate using a single shared buffer with a custom-designed concurrency scheme? Is that a natural thing to express in Erlang? It's a natural thing to express in Golang.
I feel like you didn't really engage with the string processing point I made.
I feel like it doesn't help an Erlang advocate's point too much to observe that Golang stole the best parts of Erlang.
I feel like every time it is ever pointed out in any language comparison that Golang can produce runnable native binaries, someone always has some rube-goldbergian alternative that nobody in the real world ever uses to get their own language bootstrapped onto some routine from a single file.
I gave specific reasons why Golang's tooling is strong. I feel like I got a response that says "Erlang's tools are mature". Nobody is arguing that Erlang is immature. The argument is that it's geriatric. :)
Ultimately, I agree that most of what I think is good about Golang is probably icing on the cake after "the language is boring enough to be familiar to Python programmers".
> This is a taskbar button press for me.
I'd hate for anyone to lightly brush off this feature. The fact that you can you can just rsync a binary means you can just spin up a machine, not care about what libraries are installed, and just run the Go binary on it.
I know there are tools like Chef out there, but I can spin up production-ready Go application servers on any Linux box in a minute because of this feature.
Erlang shows, logs, send via mail and telepathically tells you about what died where with all the details, and then additionally restarts the failed process according to restarting policy and tries again. Or not.
Anyway, I think this thread was about comparison of Erlang and Go - other threads are already full of people writing how wonderful Go is. As a part time Erlang developer, in this thread, I'd like to read what Go has better than than Erlang, not what is good in Go in general, because the latter is just increasing noise-to-ratio.
And it's possible to build single binary and deploy it with Erlang too.
Static typing with a very convenient expression in the language.
A concurrency model that is easier to adopt if your frame of reference is concurrent C++.
A simpler, friendlier syntax, which is probably not a win if you're a veteran Erlang programmer.
Probably better tooling: native binaries, a lightning fast compiler with great error messages and testing facilities, &c.
Perhaps a more modern standard library, which is made somewhat simpler and more concise by the pragmatic adoption of a very little bit of conventional OO, without going whole hog the way Java does.
They can be. By default a `make(chan int)` is blocking, but you can give it a buffer size with `make(chan int, 1000)` or something and it won't block until it's full.
When this happens, it seems like it tends to be exposing a fault in the design of the program. In a purely asynchronous system you can obviously avoid deadlocking in interprocess communication while still having a system that never correctly converges.
Sure, a program that uses a channel with a large buffer size as if it were an asynchronous channel contains a bad bug. The point is that if you need such an asynchronous channel, Go doesn't provide it. There are many possible examples of programs that need truly asynchronous channel functionality in which using a channel with a large buffer size would expose the program to subtle deadlocks that may only manifest in the wild on large data sets.
Wouldn't a correct design for programs that occasionally needed to handle huge data sets be to consciously and deliberately serialize (or at least bound the concurrency of) some parts of the code, rather than to pretend that the program was operating on an abstraction that could buffer arbitrary amounts of data in parallel and always converge properly?
Yes. You can always build the right thing in a system with synchronous channels—there are many ways to build asynchronous channels out of synchronous channels, after all, even if they aren't built into the language. My point is that asynchronous channels make it easier to avoid accidentally shooting yourself in the foot, that's all.
For what it's worth, I don't think that Go made a bad decision here (although I personally wouldn't have made the same decision, because of examples like those I gave in my other reply downthread). Certainly synchronous channels are faster. There are always tradeoffs.
If you want async, you could really just keep consuming from the channel with goroutines until your hardware catches on fire. There's nothing in Go that is limiting this behavior.
Async communication makes it harder, but doesn't avoid it by default. E.g. if in in Erlang gen_server A calls gen_server B w/o timeout and B, to process this call, then calls A (ouch, bad design, but possible), you've got a wonderful deadlock.
I'm using both, Erlang/OTP and Golang. Both have their strengths and advantages. So simply use the right tool for the right work (and no hammer to turn a screw).
Right, you can deadlock in any actor-based system (well, not any actor-based system—I've seen research systems that provably do not, but they're research). Async communication just makes it harder to screw up, as you say.
My point is that there are actually very few concurrency problems where deadlocks are solved by increasing the buffer size by some fixed amount. If you want your code to be correct, in most cases a buffer size of 100 might as well be a buffer size of 1, except that an increased buffer size can improve performance for some scenarios.
When people talk about asynchronous channels, they usually mean that you can stream messages to another actor and know that you won't block. That is not true for Go channels. You can increase the buffer size, but that just reduces the chance that your program will deadlock: it doesn't make a Go channel work in situations where you need an asynchronous channel for your program to be correct.
then your normal program flow won't block. I guess that's what you mean by deadlock. If your program runs into an actual deadlock, the runtime will detect it, crash the program and show stacktraces.
That just gives you more complicated deadlocks. You still have to be aware of the potential for tasks to deadlock and consciously design them not to do that. It's a problem that does come up all the time in Go programs, but tends to come up quickly enough (due to the way concurrency is designed in Golang) that you fix it quickly, like an accidental nil reference in a Ruby program.
The technical barrier for Erlang is much much greater in my opinion than Go. Introducing something into our stack is a very tricky situation. We are mostly all Python engineers, so bringing in something new has to be intuitive for others to pick up on and get involved.
I like Erlang a lot, but learning Go is much easier, especially for people with a background in imperative languages. When you code in Go everything seems very familiar, even for someone who tries it for the first time; in Erlang not so much. Now, don't get me wrong, I think it is amazing to know different paradigms, but some people just can't afford that luxury (think lack of time).
There's probably no match for Erlang's error handling mechanisms out there, I actually dislike Go's panic mechanism, but if you have a small piece in your system which needs to be wickedly fast and highly concurrent I see Go as a perfectly good choice.
The type system in Go is as important as the concurrency system. Sure, Erlang can do concurrency as well as (or better than) Go, but it doesn't have the type safety that Go offers.
For one, a shared-nothing design makes migration to different machines easier and doesn't require a stop-the-world garbage collector.
Erlang is memory safe in the presence of many-core (or many-system) parallelism. Go is not (you can segfault, possibly in an exploitable way, if GOMAXPROCS > 1).
Erlang unbounded channels reduce deadlocks because your sender can continue execution without waiting for a receiver.
Have we heard a lot of stories about deployed Golang apps having problems because of garbage collection? It's true that this is a significant designed-in advantage for Erlang, which can run it's collector on a process-by-process basis.
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.
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?
> Have we heard a lot of stories about deployed Golang apps having problems because of garbage collection? It's true that this is a significant designed-in advantage for Erlang, which can run it's collector on a process-by-process basis.
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 [1]. 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.
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 [1]. It does give you control over layout of memory.
These are two sides of the same coin. Having control over memory layout allows you to implement what are in effect allocators.
Here, you will never actually block on your send since it runs on it own goroutine. I can't see an actual use case for this kind of thing but since you are using this argument over and over then.. :)
It's mostly FUD, but it's a rathole of an argument since nobody has defined what "exploitable" means. Rather than nail down the term so we can all have ourselves an even more pointed language war, we should probably just let this go.
In general I consider segfaults exploitable, because of heap spray and virtual method calls. Even if not "exploitable", I consider it "very very scary".
This is far fetched. The threat scenario that page contemplates is "what happens if you're trying to safely run untrusted Golang code, as if it was content-controlled Javascript and you were a browser". It's not the case that Golang in its natural environment gives attackers the ability to paint the heap with malicious addresses and then provide themselves with a statistically significant shot at corrupting memory to exploit those addresses.
Your point is a reason that Golang couldn't be dropped in as a browser Javascript replacement. But nobody has ever suggested that it could be; it can't, just like Java (which was designed for the purpose but failed at it) and Erlang (which wasn't) can't.
> It's not the case that Golang in its natural environment gives attackers the ability to paint the heap with malicious addresses and then provide themselves with a statistically significant shot at corrupting memory to exploit those addresses.
What if a Go program allowed Go objects to be scripted by untrusted user code written in JavaScript? In browsers it is very possible to corrupt the Frame (Gecko)/RenderObject (WebKit) tree, which is in a C++ heap that is separate from the JavaScript heap.
The memory safety issues in C++ are a problem not because they mean that untrusted code written in C++ can't safely be executed (although it does mean that). It also means that safe languages become easily weaponizable. JavaScript (or Lua, or whatever) embedded in a Go program could paint the heap with malicious addresses.
This argument reduces to, "what if a Golang process exposed enough of its runtime to Javascript so that Javascript would be able to simulate an attacker just having access to Golang in the first place". In reality, if you were wacky enough to bolt Javascript onto Golang, you probably wouldn't do it in a way that would enable heap spray exploits, even if you had no idea what a heap spray exploit was.
The Javascript/C marriage problem isn't "heap spraying", it's that the interpreters themselves are full of exploitable C bugs, which is made much worse by the fact that the Javascript object lifecycle is expressed in an inherently unsafe language and so every tuple of [reference, event] has to be diligent checked. The same simply wouldn't be the case for any realistic marriage of Golang/Javascript, if only because the number of exploitable code conditions in Golang is miniscule compared to that of C.
All heap spraying does is make bugs that are very plausible to exploit easy to exploit reliably. You still have to start with "plausible".
> All heap spraying does is make bugs that are very plausible to exploit easy to exploit reliably. You still have to start with "plausible".
I'm not confident that race conditions in a shared-everything language are not "plausible". My experience is that race conditions are subtle and hard to find, even with a race detector. All you have to do is race on a map or a slice. And virtual calls are everywhere in Go.
Sure, we don't know that it's a problem so far, as nobody has created such a scenario. We're in violent agreement there. I grant that for server-side use cases, it doesn't matter—people use enormous C++ server codebases in production all the time and memory safety issues rarely bite them to the same degree that we see in browsers.
All I'm saying is that I don't have the same level of confidence that Go is free from memory safety exploits that I have for, say, Erlang or Java.
The use-after-free bugs that people are heap-spraying to exploit are plausible because the people who find them can tell you a simple story about how a program writes attacker-controlled data to attacker-controlled addresses. Unlike the Golang hypothetical you offer, they aren't plausible just because someone says they are.
You're wildly off the mark when you say that C++ serverside code tends to survive against attackers looking for memory corruption bugs. They do not. They fail with memory corruption flaws routinely. That was a cheap shot (you tried to create an equivalence class of unsafety between two totally unrelated languages and two totally unrelated sets of bug classes) and it won't work. You're going to have to try harder to make a case, if it's worth it to you.
Nothing is as bad as browser Javascript (it would be hard to conceive of a harder software security problem to design against), but C++ server software is pretty far towards the "unsafe" side of the security spectrum, and Golang and Erlang probably occupy virtually the same spot on that spectrum.
Unfortunately, I think we're basically at an impasse here.
I've described a scenario whereby a Go program that embedded untrusted safe code could fall to memory safety vulnerabilities. To be exact, it creates a slice of interfaces and accesses the slice in a racy way in such a way that it calls virtual functions at the same time it inserts, causing the slice to be reallocated. Then an attacker sprays the heap with addresses of shellcode. This results in arbitrary code execution when calling a virtual method.
You're saying that this is so unlikely as to be implausible, as it's never been observed in practice and might not even work. That's fine, I respect that position. We'll leave it there, and agree to disagree about whether this is a concern relative to languages like Erlang that are designed to be 100% free of memory safety problems. :)
What you've done in this comment is recapitulate the idea of a web browser executing content-controlled code, which is a problem that neither Erlang nor Golang could safely solve, and which no reasonable designer would ever use Erlang or Golang to solve, but layered on just enough abstraction to make that observation sound symptomatic of a problem with Golang.
I'm not trying to be pissy about it; I make bogus arguments all the time too, often without realizing it. You obviously know what you're talking about. I just think in this one subthread, you're wrong.
I don't dispute that segfault almost always means exploitability (I've seen friends write amazing exploits using only the slightest restricted memory corruptions).
But the data races don't bother me at all, first of all you won't encounter them if you embrace a program design facilitating goroutines and channels (the often quoted "don't communicate by sharing, share by communicating"), and second because we now have the tools to detect data races.
Segfaults do not almost always mean exploitability. In fact, the largest class of segfaults (un-offsetted NULL pointer dereferences) are rarely exploitable. The argument that says "look at that program, it segfaulted, it is probably exploitable" is not really valid.
honestly, I don't know. I know Go does concurrency well and I like it, the only reason I said it was equivalent or better was to avoid a flamewar. I've never even used Erlang.