Origins and philosophy
Go, created by Google in 2009 by Robert Griesemer, Rob Pike, and Ken Thompson, was born with a precise goal: simplicity. Its creators wanted a language that was easy to learn, fast to compile, and ideal for developing distributed services and concurrent systems.
Rust, initially developed by Mozilla Research in 2010, has a different mission: to offer performance similar to C/C++ while ensuring memory safety without a garbage collector. Rust's motto could be "zero-cost abstractions" with "memory safety".
Memory management: two philosophies compared
Here we find the most fundamental difference between the two languages.
Go uses an automatic garbage collector. This means developers don't have to worry about manually allocating and deallocating memory. Go's GC has been optimized over the years to reduce pauses (latency), making it suitable even for low-latency applications. The downside? Performance overhead and limited control over memory management.
Rust instead adopts the ownership system, a unique system that guarantees memory safety at compile-time without runtime overhead. Each value has an "owner", and when the owner goes out of scope, memory is automatically freed. The borrow checker verifies that there are no data races or invalid pointers at compile time.
// Go - automatic management with GC
func process() {
data := make([]byte, 1024)
// use data...
// GC will handle cleanup
}
// Rust - explicit ownership
fn process() {
let data = vec![0u8; 1024];
// use data...
// memory freed automatically here
}
Performance and control
Both languages are compiled to native code and offer excellent performance, but with different nuances.
Go is fast and predictable. Compilation times are exceptionally quick, fundamental for iterative development cycles. Runtime performance is excellent for most web applications and microservices, although the garbage collector introduces minimal overhead.
Rust offers performance comparable to C/C++, often superior to Go in CPU-intensive scenarios. The absence of a garbage collector and granular memory control allow for extreme optimizations. The downside? Compilation times are significantly longer.
Learning curve
Go was designed to be learned in a few days. The syntax is minimal, there are few keywords, and conventions are clear. A developer with experience in languages like Python or PHP can be productive in Go very quickly. Personally, I found the transition very natural.
Rust has a notoriously steep learning curve. Concepts like ownership, borrowing, lifetimes, and the borrow checker can frustrate even experienced developers in the initial phases. However, once these concepts are understood, they offer unparalleled control and safety.
Concurrency: goroutines vs async/await
Go excels in concurrency management. Goroutines are lightweight threads managed by the runtime, incredibly cheap to create (you can have thousands or millions). Channels offer an elegant mechanism for communication between goroutines.
func main() {
ch := make(chan string)
go func() {
ch <- "message"
}()
msg := <-ch
fmt.Println(msg)
}
Rust offers different concurrency models. The ownership system prevents data races at compile-time. It supports both traditional threading and async/await, but requires choosing a runtime (like Tokio) and a deeper understanding of concepts.
use tokio;
#[tokio::main]
async fn main() {
let handle = tokio::spawn(async {
"message"
});
let msg = handle.await.unwrap();
println!("{}", msg);
}
Ecosystem and tooling
Go offers a "batteries included" experience:
- -
go modfor dependency management - -
gofmtfor automatic code formatting - -
go testfor integrated testing - - Integrated tooling for profiling and debugging
- - Fast compilation
Rust has Cargo, probably the best package manager in the ecosystem:
- - Excellent dependency management with crates.io
- - Integrated build system
- - Testing framework
- - Documentation generation
- -
rustfmtandclippyfor code quality - - Compilation is slower but produces optimized binaries
Ideal use cases
When to choose Go:
- - Microservices and REST APIs
- - Web backend applications
- - DevOps tools and CLI
- - Distributed systems
- - Cloud-native services
- - When immediate productivity is needed
- - Teams with diverse skill sets
When to choose Rust:
- - Embedded systems
- - Game engines and graphics
- - Performance-critical applications
- - Operating systems and drivers
- - WebAssembly
- - Cryptography and security
- - When every millisecond counts
- - When memory safety is critical
Error handling
Go uses the explicit pattern of returning errors:
func readFile(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
return data, nil
}
This approach is verbose but clear. Every error must be explicitly handled.
Rust uses the type system with Result<T, E> and Option<T>:
fn read_file(path: &str) -> Result<Vec<u8>, std::io::Error> {
std::fs::read(path)
}
// With the ? operator
fn process() -> Result<(), Error> {
let data = read_file("file.txt")?;
Ok(())
}
Rust's approach is more functional and type-safe.
Community and adoption
Go is widely adopted, especially in cloud and DevOps. Docker, Kubernetes, and Terraform are all written in Go. The community is mature and there are abundant learning resources.
Rust has a passionate and growing community. It has been voted as the "most loved language" on Stack Overflow for several consecutive years. Adoption is growing rapidly, especially in areas where C/C++ were dominant.
The "rewritten in Rust" phenomenon: a necessary reflection
In recent years we've witnessed a real race toward "rewritten in Rust". Traditional utilities, CLI tools, and even entire operating system components are being rewritten in Rust, often with great media emphasis. But we need to make an important reflection:
Software doesn't become safer just because it's written in a language that promises memory safety.
Rust guarantees memory safety, and that's an extraordinary achievement. But software security is a much broader concept that includes:
What Rust does NOT solve automatically
Logic bugs: An algorithm implemented incorrectly remains incorrect, regardless of the language. If your business logic has a flaw, Rust won't flag it.
Application-level security vulnerabilities: SQL injection, XSS, CSRF, authentication bypass, privilege escalation. These vulnerabilities exist at the application design level, not memory management.
Design flaws: Poorly designed architecture produces fragile software in any language. Badly designed APIs, anti-patterns, wrong architectural choices have nothing to do with the language.
Unsafe dependencies: Your code can be memory-safe, but if you use a crate with vulnerabilities, the problem remains. The dependency ecosystem is vast, and supply chain security is a critical issue.
Configuration errors: Many vulnerabilities arise from incorrect configurations, poorly set permissions, exposed secrets. Rust can't do anything about this.
Human errors: Hardcoded passwords, API keys committed to public repositories, deployment errors - these are human problems, not technical ones.
The cost of rewrites
Rewriting existing and stable software in Rust has significant costs:
Time and resources: A complete rewrite requires months or years of work. That time could be invested in new features or improving the existing codebase.
Bug regression: Every rewrite introduces the risk of reintroducing already-solved bugs or creating new ones. Legacy software, while not perfect, has accumulated years of bug fixes and handled corner cases.
Loss of tribal knowledge: Existing code often contains solutions to non-obvious problems, workarounds for edge cases documented only in the code itself.
Ecosystem maturity: A tool written in C or Go for 10 years has a mature ecosystem, extensive documentation, established community. The Rust version must rebuild all of this.
When rewriting in Rust makes sense
I don't want to demonize Rust rewrites, in some cases they make perfect sense:
- - When there are evident memory safety problems in existing code causing recurring CVEs
- - For critical components where performance and security are absolutely priorities (e.g., parsers, codecs, cryptography libraries)
- - When software is in active maintenance and a major refactoring is already being planned
- - For new projects where there's no legacy to maintain
- - When the team has Rust competencies and can maintain the code long-term
The rewrite craze
The developer community loves to rewrite. It's in our nature: to see legacy code and think "I would do it better". But this bias makes us systematically underestimate:
- - The complexity of existing software
- - Use cases we don't know about
- - Historical reasons for certain choices
- - The real time necessary
The "rewritten in Rust" phenomenon is often driven more by enthusiasm for technology than by real needs. It's like the old craze to rewrite everything in Node.js, or before that in Ruby, or before that in Java.
A pragmatic approach
Before rewriting something in Rust, ask yourself:
- What's the real problem I'm solving? Is it really a memory safety problem or another type of problem?
- Are there less drastic alternatives? Can I improve the existing code? Can I use better tooling, static analysis, fuzzing?
- Do I have the necessary resources? Team, time, skills to complete and maintain a full rewrite?
- Is the current software really problematic? Or does it work well and I just want to use new technology?
- What would happen if I invested this time in something else? New features? Better documentation? Developer experience?
Rust is a tool, not a religion
Rust is an excellent language with unique characteristics. But it remains a tool. Like Go, like Python, like PHP, like any other language, it has a domain where it excels and cases where it's overkill.
Memory safety is important, but it's not the only aspect of software quality. A well-written, tested, reviewed Go program with good security practices is infinitely better than a poorly written Rust program with logical vulnerabilities and design flaws.
Software quality derives from the quality of developers, processes, and design care - not from the programming language.
Instead of chasing the next technology hype, we should focus on:
- - Writing readable and maintainable code
- - Implementing comprehensive tests
- - Doing serious code reviews
- - Documenting well
- - Using static analysis and security scanning
- - Following security best practices
- - Truly understanding our application's security problems
"Rewritten in Rust" is fine when motivated by real needs. But when it becomes a cargo cult or an excuse to chase technology hype, we're simply wasting resources that could be used to actually improve the security and quality of our software.
There is no absolute winner. The choice between Go and Rust depends on context:
Choose Go if:
- - You want to be productive quickly
- - You're building web services or microservices
- - The team has heterogeneous skills
- - Simplicity and maintainability are priorities
- - The garbage collector isn't a problem
Choose Rust if:
- - You need maximum performance
- - Memory safety is critical
- - You work on embedded or low-level systems
- - You can invest time in learning
- - Total control over memory is necessary
Personally, in my experience with Go, I greatly appreciated the productivity and simplicity for web projects and microservices. Rust certainly represents the next step for those who want to push toward domains where control and performance are fundamental.
The good news? Both languages will continue to evolve and coexist, each excelling in its own domain. And the choice of language, however important, remains secondary to the quality of design, processes, and the people developing the software.