Hacker News new | past | comments | ask | show | jobs | submit login

How does Go compare to Rust these days with respect to language maturity, community size and general maturity/availability of libraries?

So far I've found Rust more interesting and pushes the envelope a bit more. But what is the sales pitch for Go?




The way I view it is that Go is for people who write software while Rust is for those who like to think about how to write software.

Perhaps I’m old and grumpy but I feel that at this point I’ve had every language discussion before and if people start to moan about lack of metaprogramming support I just leave.

I have yet to see a project that failed due to lack of high order abstractions


I have yet to see a project that failed due to being written in Assembly, yet we moved on to better abstractions.


I’ve worked on two projects (in early x86 assembler) that ran out of steam because they took too long to do and it was hard to onboard new developers.

I’ve also abandoned a few paths during projects because of libraries that were overly abstract, didn’t work and were thus hard to understand and maintain.

A product at a company I used to work for had to reimplement a huge chunk of code because the original designer left and the metaprogramming behemoth he wrote was too hard to understand, extend and debug. That one chunk of code cost about $500k and 6 months to replace. I’m unsure of the total cost of that effort as it delayed the next release at a critical time.

Abstraction levels need to be appropriate. Enough to make things simple - not so much that it makes life harder.



I would have to look at this since I’m not familiar with the codebase, but I’ll have a look. Thanks!

I’m not quite sure this is what I meant from the description of the problem. This looks more like a problem of trying to program Java in Go. What I was thinking of was more along the lines of modeling things in ways that either disconnect you from the problem you are trying to solve or solve it with too much abstract cruft grafted onto the model so just dealing with the abstractions themselves becomes more work than a more direct and naive approach.


> The way I view it is that Go is for people who write software while Rust is for those who like to think about how to write software.

More accurately speaking, Rust forces people writing software in a specified way. You need to think about how to write software much less in Rust than in Go. In Go, you really need to think more to choose a better solution from many potentials.


Anecdata: he Rust people I know do a lot of talking. They have very little running in production. The Go people I know do less talking and tend to ship a lot more code into production. Or more precisely: more value per month.

And mind you: I manage quite a few projects where Rust would be a great fit because it would replace C/C++, but for embedded projects Rust just isn’t getting the traction it needs to convince key industry players.


I think the selling points with Go are:

1. Cross-platform 2. Fairly mature ecosystem 3. Pretty easy to pick up coming from either a dynamic or static language background 4. Somewhat simpler approach to concurrency (although I've created my share of race conditions) 5. Backed and used by Google (if that is positive for you as a developer) 6. Is a pretty good multi-purpose language

I think in comparison to (my limited understanding of) Rust, Go is beginner friendly. Also, I think Rust has most of the above covered as well, but it seems like there are lower level primitives with functional aspects built-in. Haven't needed these in Go, so haven't investigated them there.


I just wish there were more jobs for either. They're both much less popular than I expected.


If only people stopped to use Python for absolutely everything.


It depends on which fields of your expected jobs are. Go is the language of cloud infrastructure, and half of blockchian projects are written in Go.


What cloud infrastructure?

On my bubble everything cloud is either Java or .NET, alongside VM images, and native OS containers.

Azure and AWS also have hardly any major tech done in Go.


PaaS platform: docker, rkt, k8s, openshfit, gvisor, kata-container, etcd, consul (and more hashicorp tools), istio, linkerd2, flannel, calico, wavenet, traefik, vitess, nat, ...


Right. I'm not interested in either of those fields. I want to use Go instead of Java/Scala, but there is little appetite.


“Go is to services what Rust is to systems.”


Without cargo though


Genuine question: What does Cargo do that Go 1.11 modules can't?


Applying the bug fixes in a dependency I'm using when a new version of it is published… [1]

Sarcasm apart, I wasn't talking about features. But cargo is a really well-designed package manager. I've been using Rust full-time for 2 years now and it had never annoyed even once.

[1] the sarcasm was referring to the Minimum Version Selection algorithm used by go's package manager, which is a really bad case of NIH from Go's teams who decided to throw away a thriving community work to do their own stuff, ending up with something different from what everybody else has been for more than 20 years. And unsurprisingly, it's really bad…


> Applying the bug fixes in a dependency I'm using when a new version of it is published…

You can replace a dependency with a local modified copy of the dependency to do this.

Minimum Version Selection is for security reason. It might has some disadvantages, but personally I like it. It gives programmers more control.


Would it be fair to say that Go's concurrency support makes it a good match in the network services space? I see Go more often in Docker and cloud contexts.


Personal opinion: no. Go's concurrency is hard to control or monitor, and it's hard to make higher level abstractions that are both safe and convenient. It's of course possible, but the tradeoffs are rather severe.

Go's concurrency shines best in short-lived single-purpose processes, which is a near perfect fit for CLI tools. When you don't care if something gets abandoned or fails to make progress and can just ctrl-c, the relative simplicity is 100% worth it. Other parts of Go work well here too, especially the very low startup overhead (unlike, say, python). For long lived processes though, like most services, it feels very error-prone or very boilerplatey and manual.


> Go's concurrency shines best in short-lived single-purpose processes, which is a near perfect fit for CLI tools

This is not reasonable. Go's concurrency shines for its flexibility. It has nothing related to whether or not the written programs are short/long-lived. It is the only language which gives me a happy and fun experience in concurrent programming.

> For long lived processes though, like most services, it feels very error-prone or very boilerplatey and manual.

This is not reasonable too. Whether or not a written program is robust depends on the experience of the authors. Rust may reduce many mistakes at compile time, but it can't prevent all mistakes. Programmers still need experience to avoid many mistakes.


That's kinda like saying "language ergonomics and api design don't matter, you just need to be careful".

Which is true to some degree, you can build anything in anything if you're careful enough. But programming history is full of repeated, preventable errors, and languages where X error is impossible or much less likely if you're less than perfect.

Go's rather far down "unsafe" in my books, especially in regards to concurrency and error handling. For simple code, it's pleasantly simple. For larger, robust code, it's several times more verbose than other langs I've used, and often requires continually re-implementing common "wheels" (that should not usually be written by hand, as they're finnicky to get right, which is also easy to miss in code review) to get around a lack of generics.


Go's concurrency features provide a larger super set of possibilities of other language. If you only do concurrency programming in a simple way (just as most languages), it is hard to make some mistakes even if you are a moderate Go programmer. On the other hand, Go allows you to do concurrency programming with many creative ideas which are hard to apply in other languages. Surely, you should be an experienced Go programmer to implement these creative ideas.

Rust does prevent some errors at compile time, but achieving this has its own cost, such as sacrificing flexibility and simplicity. For many use cases, the sacrifices are not worth it.

> it's several times more verbose than other langs I've used, and often requires continually re-implementing common "wheels"

If the common wheels need generics to implement, I have nothing to defend. I can only say generics will come to Go eventually.


Could you expand on what you think is hard about the concurrency in Go? (And perhaps give an example of a language that makes it easy/easier?)


> Could you expand on what you think is hard about the concurrency in Go?

Go's concurrency is cheap[0] multithreading (shared-memory concurrency). It has a tagline that you should not share memory ("Do not communicate by sharing memory; instead, share memory by communicating"), however nothing prevents sharing memory (so it will eventually leak in) and channels are pretty slow[1] so it's not uncommon to go back to regular shared-memory primitives (with no safety net).

You've got no idea what your application state really is (even less so once things start falling over), and there are no built-in tools for reliability e.g. you can't get notified that a goroutine has keeled over or anything unless you implement that yourself.

> And perhaps give an example of a language that makes it easy/easier?

Erlang / elixir was built specifically with that in mind. It uses lightweight shared-nothing concurrency (non-OS processes) and the system builds-in ways for processes to have oversight over others (e.g. monitors & supervisors) or get killed alongside them (links) and provides behaviours which bundle known patterns, etc…

[0] as in "not expensive", as in uses quite a bit less memory & especially kernel resources than OS threads or processes[1]

[1] though this point should not be too overstated, people commonly misuse "the C stack is XMB" with "creating a thread consumes XMB of physical memory", a C stack is mostly a regular allocation and thus usually just VSS until actually exercised

[2] and not necessarily a huge gain in bug-freedom: https://songlh.github.io/paper/go-study.pdf


Thanks for taking the time to expand on that.

I haven’t really benchmarked channels since the bottlenecks in code I write tend to be I/O, but I’ll play around a bit to understand that better.


Not the OP, but if you’ve ever had to write 100% fault tolerant concurrent Go code, you find yourself having to add a lot of monitor-recover-restart “boilerplate” that ends up being quite complex, depending on the task at hand.

It helps if you follow certain principles like making your concurrent tasks idempotent etc., but Go doesn’t force you to write things that way, so it takes a bit of remembering each time.

If the task is relatively simple, you end up writing a lot of concurrency infra for a little bit of concurrency, which can become frustrating if you’re doing it for the Nth time.

It sounds like a problem that’s ripe for abstraction, but it turns out that it’s quite hard to build abstractions that get all the trade offs just right for each case. There’s some worker pool style libraries out there, but if you care about performance you can usually eke more out by writing a custom solution.

As for alternatives, I have heard that erlang has a better story for this, because of the inherent restrictions of the language, but I haven’t used it much.

In my experience, Go gives you enough rope to make this task “merely annoying” (rather than impossible or astoundingly challenging) without giving you enough to truly hang yourself with. You can get really tangled up though :)


Rust has broadly the same concurrency support, with a more powerful compile-time race detector (but one that also comes with a learning curve). The main difference is that Go uses M:N threading, while Rust uses 1:1 threading with optional explicit async/await constructs.


Rust is really great, but I feel like you are overselling it a bit here:

- async/await is available in the nightly version of Rust (1.39), but not in the stable version (1.37).

- 1:1 threading is not strictly equivalent to M:N threading (the main practical difference being that the stack size per OS thread is larger than the stack size per goroutine).

Writing that "Rust has broadly the same concurrency support" is misleading right now. But it could be true in a near future.


The features that each language offers are the same: threads, channels, and blocking I/O (though Rust has more features for safe concurrency--Rust prevents data races statically, while Go is not even memory safe in the presence of such races). What is different is the performance characteristics. In some cases, M:N will be more efficient; in some cases, 1:1 will be. But just as I wouldn't say Go is lacking FFI features because M:N makes cgo slow, I wouldn't say Rust is lacking concurrency features.


> blocking I/O

Rust I/O are strictly similar to Go when using 1:1 OS threading, but become conceptually different when using async/await.

> Rust has more features for safe concurrency--Rust prevents data races statically, while Go is not even memory safe in the presence of such races

Agreed. That's a big advantage of Rust.

> What is different is the performance characteristics.

Performance is a feature. It's significant enough to justify using one language or the other depending on the project at hand.

> In some cases, M:N will be more efficient; in some cases, 1:1 will be.

The main advantage of the M:N model, compared to the 1:1 model, is the memory usage, because each goroutine starts with a small stack (a few kB). It makes possible to start a larger number of M:N goroutines than 1:1 threads.

> But just as I wouldn't say Go is lacking FFI features because M:N makes cgo slow, I wouldn't say Rust is lacking concurrency features.

Go's FFI works but is slow. It's a well known fact. Rust's concurrency story is not stabilized yet (areweasyncyet.rs). It's a fact too. I don't see a problem with acknowledging both :)


> The main advantage of the M:N model, compared to the 1:1 model, is the memory usage, because each goroutine starts with a small stack (a few kB).

Rust's async-fns compile into state machines, so they only allocate exactly the number of bytes that they need.


I know. I was writing about the 1:1 model without async/await :)


For the threading difference, do you mean that Rust needs 'normal' threads plus async/await support, but Go can just lean on its built in concurrency support for both cases? i.e. Go threads instead of explicit async style code.


Your sentence implies that Go green threads can do both of what 1:1 threading and async/await can, but it's more complex than that: there's a tradeoff between the simplicity (only one concurrency primitive) and the capability of the said primitive:

- with it's M:N model, Go cannot really do FFI efficiently, while both 1:1 async/await have no problem with that in Rust.

- goroutines are cooperatively scheduled, while OS thread are preemptively scheduled. If you have hot loops, Go's model won't be able yo guarantee fairness between goroutines, which may be a problem depending on your usage. OS threads don't have this problem.

- with goroutines you won't achieve the level of performance you can reach with async/await.

Go and Rust fills a different niche of programming and take a different stand on this tradeoff: Rust gives you more power at a complexity cost. Go offers you more simplicity, with less power.


The reason cgo is (relatively) slow is small stacks, not M:N threading.

Small stacks (and M:N threading) are needed to efficiently implement tens of thousands of goroutines.

CGO is slow mostly because it needs to switch to a larger stack when calling a C function.

It's a trade-off.

A different Go implementation could make Goroutines map 1:1 to threads and have fast cgo calls at the expense of slow goroutines.

Rust is not exempt from those trade-offs. They chose fast FFI and smaller runtime. They paid with slow threads.

Async/await promises to be the best of both worlds but it comes at a cost of great complexity, both for the programmer and the implementor.

At the end of the day under the covers it's just threads that need to be managed in a complex and often invisible way plus a complex rewrite of your straightforward code into a mess of a state machine.

I can confidently say that learning to use goroutines took 10x less time than learning async/await in C#.

I understand goroutines better than I ever understood async/away.


> Small stacks (and M:N threading) are needed to efficiently implement tens of thousands of goroutines.

Tens of thousands of threads are no problem with 1:1 threading on Linux.

> They paid with slow threads.

I would not call 1:1 threads "slow threads". If they were slow, then the 1:1 NPTL would have not defeated the M:N NGPT back in the day when this was being debated in the open source OS community.

> complex rewrite of your straightforward code into a mess of a state machine.

The entire point of async/await is that you don't have to do a complex rewrite of your straightforward code. It remains straightforward, and the compiler does the transformation for you.


> tens of thousands of threads are no problem with 1:1 threading on Linux

Yes it's a problem, even on modern kernel, memory, context switch ... there is a reason why high performance code uses small thread pool.


Given the context, it's not a memory problem was implied, because small stacks are not actually relevant, because C stacks are not committed upfront (at least on unices, not sure about linux). So your threads are more likely to take up a page each (plus kernel datastructure overhead) than the 8MB allowed to a C stack.


> It's a trade-off.

Yes, That's exactly my point.

> I can confidently say that learning to use goroutines took 10x less time than learning async/await in C#.

It's a matter of preferences, I've always preferred async/await to threads because you don't need to juggle with channels and you're way less likely to cause a deadlock.

> it comes at a cost of great complexity, both for the programmer and the implementor.

Is that really harder to implement than a M:N runtime like Go's?

> The reason cgo is (relatively) slow is small stacks, not M:N threading.

You're picking nits here. Without small stacks there would be no point using M:N threading in Go…

And Cgo is not only relatively slow: most of the time it is slow enough to destroy all performance benefit of calling a FFI function. It's a pretty big deal.


Yes, Go is in many ways a DSL for writing servers. IMO it should be the default language you reach for when starting a new server project.


Do you include web development in that ?


Rails-style, ORM-centered, frontend+backend web development? No.

Something different than that? Yes.


Rust has no backward compatibility, a very disputable feature with language revisions when compiling, and it is rare for you to have a large package from a stable branch without loading the package from the test branch. So in its present form it looks interesting, but it is absolutely not adapted to use outside of pet projects.




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: