Rust vs Go in 2025
Can I say something without everyone getting mad?
Which is better, Rust or Go—and does that question even make sense? Which language should you choose for your next project in 2025, and why? How does Rust compare with Go in areas like performance, simplicity, safety, features, scale, and concurrency?
TL;DR these are two great, but very different languages, both of which are worth your time and attention. Let’s see why.
2025 update
This post has been regularly updated over the past few years to reflect the changing landscape of both Go and Rust, and in some cases one language has edged ahead of the other in certain areas. It’s fair to say that perhaps the balance will continue to change a little in favour of Rust, as that language is under much more active development—but we’ll see what happens.
For the moment, this post reflects my personal thoughts on the current strengths and weaknesses of the two languages, as an enthusiastic user and teacher of both. Programming languages arouse strong feelings, and none more so than Go and Rust. Many people may disagree violently with what I have to say here, and that’s okay. With that caveat, now read on.
Rust and Go are both awesome
First, it’s really important to say that both Go and Rust are absolutely excellent programming languages. They’re modern, powerful, widely-adopted, and offer excellent performance. It probably doesn’t make much sense to choose anything other than Rust or Go for new projects these days, and many companies are moving as fast as they can to reduce their dependence on older, legacy languages.
In my book The Secrets of Rust: Tools, I describe Rust this way:
Graydon Hoare, the originator of Rust, described it as “technology from the past, come to save the future from itself”. He wanted to build, not a new, exciting, and experimental programming language, but a solid, boring, reliable language, based on proven ideas that work. A language, in other words, that would incorporate the many hard lessons we’ve learned about good software development over the past hundred or two years.
And here’s how I describe Go in my book For the Love of Go:
Go is humble and pragmatic: it doesn’t have all the high-tech features and theoretical advantages of some other languages. Indeed, it deliberately leaves out many features that are big selling points of other languages. Its designers were less interested in creating an impressive programming language, or in topping popularity polls, than in giving people a small and simple tool for getting useful work done in the most practical and direct way possible.
So perhaps Rust and Go have more in common than you might think! But they are, of course, very different languages in practice, and neither of them is the perfect tool to solve every problem.
In this article, I’ll try to give a brief overview of where I think Go is the ideal choice, and where I think Rust is a better alternative.
Similarities
What are some of the common goals of both languages?
Memory safety
Historically, one of the biggest causes of software bugs and security vulnerabilities has been accessing memory unsafely or incorrectly.
Rust and Go deal with this problem in different ways, but both aim to be smarter and safer than other languages about managing memory.
The genius of Go is that it has a garbage collector. The genius of Rust is that it doesn’t need one.
Fast, compact executables
They’re both compiled languages, which means your programs are translated directly to executable machine code, so that you can deploy your program as a single binary file. This also makes both Rust and Go programs extremely fast in comparison to interpreted languages such as Python or Ruby.
General-purpose languages
Rust and Go are also both powerful, scalable general-purpose programming languages, which you can use to develop all kinds of modern software. Both have excellent standard libraries and a thriving third-party ecosystem, as well as great commercial support and a large user base.
Pragmatic programming style
While both Go and Rust have features associated with functional and object-oriented programming (OOP), they’re pragmatic languages aimed at solving problems in whatever way is most appropriate.
Neither is, strictly speaking, “object-oriented”:
We can debate about what an object-oriented language is, but it’s fair to say that the style of object-oriented programming that C++, Java, or C# users would expect is not present in either Go or Rust.
—Jack Mott
Development at scale
Both Rust and Go have some useful features which make them suitable for programming in the large, whether that means large teams, or large codebases, or both.
For example, both Rust and Go use a standard code formatting tool
(gofmt
for Go, rustfmt
for Rust), putting an
end to useless arguments over where to put your brackets.
Both also have excellent, built-in, high-performance standard build and dependency management tools; no more wrestling with complex third-party build systems and having to learn a new one every couple of years.
Building Go and Rust code, having come from a Java and Ruby background in my early career, felt like an impossible weight off my shoulders. When I was at Google, it was a relief to come across a service that was written in Go, because I knew it would be easy to build and run. This has also been true of Rust, though I’ve only worked on that at much smaller scale. I’m hoping that the days of infinitely configurable build systems are dead, and languages all ship with their own purpose-built build tools that just work out of the box.
—Sam Rose
Differences
With all that in mind, and seeing that both languages are so well-designed and powerful, you might be wondering what all the holy wars are about (me too).
Why do people make such a fuss about ‘Go versus Rust’, getting into angry social media spats and writing long blog posts about how only an idiot would use Rust, or that Go isn’t a real programming language, or whatever. It’s all good knockabout stuff, but it doesn’t exactly help anyone to decide which language to use for their project.
A wise person doesn’t make important choices based on who shouts the loudest, and while Rust and Go have a lot in common, there are also a few areas where a reasonable person might prefer one language over the other, to meet the specific needs of their project. Let’s take a tour.
Performance
Both Go and Rust are very fast. However, Go is primarily designed for speed of development (including compilation), rather than speed of execution. The Go compiler doesn’t spend a lot of time trying to generate the most efficient possible machine code; it cares more about compiling lots of code quickly. So Rust will usually beat Go in run-time benchmarks.
Rust’s run-time performance is also consistent and predictable, because it doesn’t use garbage collection. Go’s garbage collector is very efficient, and it’s optimised to make its stop-the-world pauses as short as possible (and getting shorter with every new Go release). But garbage collection inevitably introduces some unpredictability in the way programs behave, which can be a serious issue in some applications, such as embedded systems.
Because Rust aims to give the programmer complete control of the underlying hardware, it’s possible to optimise Rust programs to be pretty close to the maximum theoretical performance of the machine.
This makes Rust an excellent choice for areas where speed of execution beats all other considerations, such as game programming, operating system kernels, web browser components, and real-time control systems.
Simplicity
It doesn’t matter how fast a programming language is if nobody can figure out how to use it. Go was deliberately conceived as a reaction against the ever-growing complexity of languages like C++; it has very little syntax, very few keywords, and, indeed, few features. This means it doesn’t take long to learn the Go language to the point where you can write useful programs in it.
Go is incredibly easy to learn. I know this is an often-touted benefit, but I was really surprised at how quickly I was able to be productive. Thanks to the language, docs, and tools, I was writing interesting, committable code after literally two days.
—Early Impressions of Go From a Rust Programmer
Go is a small language, by design: it has very little syntax, few keywords, and as few language constructs as it can get away with. You can learn the basics of Go and be productive in the language very quickly.
That gives Go the advantage in projects with a short timescale, or for teams that need to onboard lots of new programmers quickly, especially if they’re relatively inexperienced.
With Go, you get things done—fast. Go is one of the most productive languages I’ve ever worked with. The mantra is: solve real problems today.
—Matthias Endler
On the other hand, the very simplicity of Go means that you need to write more code to solve certain problems: the language does less of the heavy lifting for you. It also doesn’t suit some programmers who like a very rich, expressive language that gives them many different ways to solve a problem.
Features
At the other end of the scale, Rust has just about every feature you could imagine in a programming language, and some you probably can’t. That makes it a powerful and expressive language, with lots of different ways to do the same thing.
Rust supports more complexity than several other programming languages, therefore, you can achieve more with it.
—Devathon
If you’re transitioning to Rust from some other language, you can probably find Rust equivalents for most of the features you’re used to. That gives Rust the advantage when large projects need to be migrated from a traditional language such as C++ or Java.
Rust competes for mindshare with C++ and D for programmers who are prepared to accept more complex syntax and semantics (and presumably higher readability costs) in return for the maximum possible performance.
—Dave Cheney
Rust is specifically designed to include lots of powerful and useful
features to help programmers do the most with the least code. For
example, Rust’s match
syntax lets you write flexible,
expressive logic quite elegantly:
fn is_prime(n: u64) -> bool {
match n {
0...1 => false,
=> !(2..n).any(|d| n % d == 0),
_ }
}
Because Rust does a lot, this means there’s a lot to learn, especially at the start. But that’s okay: there’s a lot to learn in C++ or Java, too, and you don’t get the advanced features that come with Rust, like memory safety.
To criticise Rust for being a complex language misses the point: it’s designed to be expressive, which means having a lot of features, and in many situations that’s what you want from a programming language. There’s a learning curve, for sure, but once you’re up and running with it, you’ll be fine.
Rust is a cleaner, more easily understood and friendlier language than most—and certainly much easier to understand than modern C++.
–Marc Schoolderman
On the other hand, the complexity of Rust, and the new way of thinking about problems that it requires, makes experienced Rust developers expensive, and it can take longer to ramp up junior developers to a level where they can be productive. Rust might not be the best choice for projects that need very rapid development or prototyping, or where cost is a bigger factor than safety and reliability.
To summarise:
Concurrency
Most languages have some form of support for concurrent programming (doing multiple things at once), but Go’s support is second to none. Instead of using operating system threads, which are relatively clumsy and slow, Go provides a lightweight solution: goroutines.
You can run millions of concurrent goroutines in a single program without creating serious performance problems, which makes Go the perfect choice for high-scale concurrent applications such as webservers and microservices.
Go also features fast, safe, efficient ways for goroutines to communicate and share data, using channels. Go’s concurrency support feels well-designed, and a pleasure to use. Because it was built in to the language from the start, instead of being an afterthought, concurrent programming in Go is simple and well-integrated.
Having lightweight syntax for spawning Go routines and using channels is really nice. It really shows the power of syntax that such small details make concurrent programming feel so much nicer than in other languages.
—Early Impressions of Go From a Rust Programmer
This makes Go the perfect choice for high-scale concurrent applications such as webservers and microservices.
Go makes it very easy to build a nicely factored application that takes full advantage of concurrency while being deployed as a set of microservices. Rust can do those things, too, but it’s arguably a bit tougher. In some respects, Rust’s obsession with preventing memory-related security vulnerabilities means that programmers have to go out of their way to perform tasks that would be simpler in other languages, including Go.
—Sonya Koptyev
Rust has most of the same concurrency features as Go, and in some areas even better ones: for example, the rayon crate provides a very elegant and lightweight way of turning sequential computations into parallel ones.
Also, many bugs in concurrent programs stem from the kind of
memory-safety errors that Rust was designed to eliminate. A good example
is the standard library’s Mutex
class: in Go, you can
forget to obtain a mutex lock before accessing something, but Rust won’t
let you do that.
Rust also won’t let you share data between threads unless you can prove to the compiler that it’s safe to do so, and while this can be annoying, the discipline this imposes is something that Go programmers have to supply for themselves, rather than it being built into the language.
Safety
Rust is carefully designed to ensure that programmers can’t do something unsafe that they didn’t mean to do, such as overwriting a shared variable. The compiler requires you to be explicit about the way you share data between different parts of the program, and can detect many common mistakes and bugs.
Rust’s very strict and pedantic compiler checks each and every variable you use and every memory address you reference. It avoids possible data race conditions and informs you about undefined behavior. Concurrency and memory safety issues are fundamentally impossible to get in the safe subset of Rust.
—Why Rust?
As a result, so-called “fighting with the borrow checker” is a common complaint among new Rust programmers. Implementing your program in safe Rust code often means fundamentally re-thinking its design, which can be frustrating, but the benefits can be worth it when reliability is your top priority.
If you’re a C or C++ programmer who’s spent weeks chasing down memory safety bugs in those languages, you’ll really appreciate Rust. “Fighting the borrow checker” becomes “The compiler can detect that? Cool!”
—Grzegorz Nosek
What’s the point of a language that doesn’t change the way you program, after all? But there are other legitimate priorities.
If you choose Rust, usually you need the guarantees that the language provides: safety against null pointers and race conditions, predictable runtime behaviour, and total control over the hardware. If you don’t require any of these features, Rust might be a poor choice for your next project. That’s because these guarantees come with a cost: ramp-up time. You’ll need to unlearn bad habits and learn new concepts. Chances are, you will fight with the borrow checker a lot when you start out.
—Matthias Endler
Scale
Go was designed to make it easy to scale both your projects and your development teams. Its minimalist design leads to a certain uniformity, and the existence of a well-defined standard style means that any Go programmer can read and understand a new codebase relatively quickly.
When it comes to software development in the large, clear is better than clever. Go is a good choice for big organisations, especially with many distributed teams. Its fast build times also favour rapid testing and deployment.
Trade-offs
Rust and Go’s design teams have made some starkly different choices, so let’s look at some areas where those trade-offs make the two languages very distinct from one another.
Garbage collection
Languages (like Go) that feature garbage collection, and automatic memory management in general, make it quick and easy to develop reliable, efficient programs, and for some people that’s the most important thing.
But garbage collection, with its performance overhead and stop-the-world pauses, can make programs behave unpredictably at run-time, and some people find this inconsistency unacceptable.
Languages (such as Rust) where the programmer must take explicit responsibility for allocating and freeing every byte of memory, are better for real-time or ultra-high-performance applications.
Go is a very different language to Rust. Although both can vaguely be described as systems languages or replacements for C, they have different goals and applications, styles of language design, and priorities. Garbage collection is a really huge differentiator. Having GC in Go makes the language much simpler and smaller, and easy to reason about. Not having GC in Rust makes it really fast (especially if you need guaranteed latency, not just high throughput) and enables features and programming patterns that are not possible in Go (or at least not without sacrificing performance).
—PingCAP
Error handling
In some languages, when a function encounters an error (for example, something like “file not found”), it can be handled explicitly, or it can be automatically propagated back to the function’s caller, and the caller’s caller, and so on, until it either finds someone willing to handle the problem, or the program crashes with an ugly error message.
Errors that trigger an automatic return from the function if not handled are called exceptions, and, while convenient for programmers, can lead to some confusing behaviour—especially if the exceptions are used deliberately as a control flow mechanism.
In both Rust and Go, while exceptions are available, their use is strongly discouraged. Instead, both languages treat errors as values, like any other piece of data, rather than as a control-flow mechanism built in to the language.
And because errors can happen with almost anything you do in a program, that means a lot of Go and Rust code is about checking error values and deciding what to do about them. However, there’s a significant difference in the way the two languages handle this problem.
In Go, error checking is clear and explicit, and usually looks something like this:
, err := getAnswer()
answerif err != nil {
return err
}
// do something with `answer`
Many people used to languages with exceptions, where errors can
simply be ignored (or “kicked upstairs” for someone else to handle),
find the proliferation of if err != nil
blocks irritating,
or excessively verbose. However, despite many alternative proposals,
none have found widespread favour, and this idiom does at least have the
benefit of making the control flow clear and unambiguous.
Rust’s error handling
is even more powerful, relying on built-in Option
and
Result
types which indicate that a return value may or may
not be present, or may instead be some error.
For example, here’s how we might write the previous example in Rust,
if get_answer
returns Option
, indicating that
there may or may not be an answer:
if let Some(answer) = get_answer() {
// do something with `answer`
}
Alternatively, if get_answer
returns
Result
, meaning that there’s either an answer or
an error, the code might look like this:
match get_answer() {
Ok(answer) => // do something with `answer`
Err(e) => // handle the error `e` somehow
}
However, Rust also provides a neat syntactic shorthand: the
?
operator causes a function to return automatically if the
Option
value is not present, or the Result
value contains an error. Since errors happen a lot, this gives Rust
programmers a more compact way to write the necessary handling code than
is available in Go:
let answer = get_answer()?;
Abstraction
The history of computer programming has been a story of increasingly sophisticated abstractions that let the programmer solve problems without worrying too much about how the underlying machine actually works.
That makes programs easier to write and perhaps more portable. But for many programs, access to the hardware, and precise control of how the program is executed, are more important.
Rust aims to let programmers get “closer to the metal”, with more control, but Go abstracts away the architectural details to let programmers get closer to the problem.
Speed
Rust makes a number of design trade-offs to achieve the best possible execution speed. By contrast, Go is more concerned with simplicity, and it’s willing to sacrifice some (run-time) performance for it.
Whether you favour Rust or Go on this point depends on whether you spend more time waiting for your program to build, or waiting for it to run.
Correctness
Go and Rust both aim to help you write correct programs, but in different ways: Go provides a superb built-in unit testing framework, for example, and a rich standard library, while Rust is focused on eliminating run-time bugs using its borrow checker.
It’s probably fair to say that it’s easier to write a given program in Go, but the result may be more likely to contain bugs than the Rust version. Rust imposes discipline on the programmer, but Go lets the programmer choose how disciplined they want to be about a particular project.
What now?
I hope this article has convinced you that both Rust and Go deserve your serious consideration. You should reject the false dilemma that you can only learn one or the other. In fact, the more languages you know, the more valuable you are as a software developer.
If you’re new to the tech industry, getting some skills in one or both of these two languages should be your priority. If you’re an established developer who doesn’t yet have Go or Rust on your resume, I think you’d be well advised to invest some time in acquiring them. Legacy languages such as C++, Python, Java, and friends will still be around for years to come, no doubt—that’s what “legacy” means—but they wouldn’t be the first things I’d pick up. New development in many large companies is now restricted to memory-safe languages only, which effectively means either Go or Rust, and probably the latter.
Every new language you learn gives you new ways of thinking about problems, and that can only be a good thing. The most important factor in the quality and success of any software project is not the choice of language, but the skill of the programmer. You will be most skilful when using the language that suits you best, and that you enjoy programming in the most. So, if the question is “Should I learn Rust or Go?”, the only right answer is “Yes.”
More resources
Check out my selections of the best Rust books and the best Go books, and you’ll find plenty of fantastic learning materials. You might also enjoy these articles:
If you’d like to take your journey further, you might like to consider studying either Rust or Go with me, in personal, one-to-one coaching and mentoring sessions. Contact me if you have any questions—I’ll be happy to chat with you.