Rust: The Borrow Checker Will Make You Question Your Life Choices
Look, I've been fighting the Rust borrow checker for three years now. First month? I wanted to throw my laptop out the window. Every compile attempt was met with error messages like "cannot borrow data
as mutable because it is also borrowed as immutable" while I'm just trying to update a fucking hash map.
The borrow checker has three rules that sound simple on paper:
- Each value has exactly one owner
- Only one mutable reference OR multiple immutable references at any time
- References must always be valid
In practice? You'll spend your first few months adding .clone()
everywhere just to make the damn thing compile. I've seen senior C++ developers with 15 years experience reduced to tears by Rust's ownership system. The learning curve is brutal, but there's a method to the madness.
Links to docs: https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html
But here's the thing - once it clicks (and it takes 3-6 months), you realize you can't go back. I haven't had a segfault, use-after-free, or data race in Rust code. Ever. When Discord switched from Go and saw 10x performance gains, it wasn't magic - it was eliminating GC pauses and lock contention that had been killing their latency.
The compile times though? Jesus. 15 minutes for a clean build is normal for medium-sized projects. Your feedback loop goes from seconds to coffee breaks.
Go: It Just Works (Until It Doesn't)
Go is the language I reach for when I need to ship something yesterday. The garbage collector handles memory for you, and most of the time it's invisible. The newer GC versions are actually pretty solid - sub-millisecond pauses in the best case, which sounds great until you realize even 100 microseconds can kill you in high-frequency trading.
Here's the thing about Go's GC - those sub-millisecond "typical" pause times become 50ms tail latencies when shit hits the fan. I've seen GC pauses during heavy allocation periods that took down production systems. The memory overhead is real too - your Go service will typically use 2-3x more RAM than the equivalent Rust version.
But the developer experience? Chef's kiss. I can onboard a junior developer in two weeks instead of six months. The race detector actually catches bugs during development instead of letting them hide until they fuck you over in production. Error handling is verbose as hell (if err != nil
everywhere) but at least you know exactly where things can fail.
The standard library is fantastic. HTTP server, JSON parsing, crypto - it's all there and it all works without pulling in 47 npm packages.
Zig: Here's a Loaded Gun, Good Luck
Zig gives you a loaded gun and a manual. The allocator system is actually brilliant - instead of hidden malloc/free
calls everywhere, you explicitly pass allocators around. Want to test memory usage? Mock allocator. Want to debug leaks? Debug allocator. Want blazing fast allocation for a specific pattern? Custom allocator.
I spent two weeks tracking down a memory leak in C++ once - turned out some asshole was calling new
without delete
in a constructor exception path. The same bug in Zig? The debug allocator spits out "Memory leak detected: test.zig:47:12" with a nice stack trace. It's like having a built-in memory profiler that actually gives a shit about helping you.
But (and this is a big but), Zig is still version 0.15.1. I've had builds break three times this year from language changes. The documentation has holes you can drive a truck through. The ecosystem is basically nonexistent - you're writing your own HTTP client, JSON parser, everything.
I love Zig's philosophy, but using it in production right now is career suicide unless you enjoy explaining to your boss why the deployment failed because the language changed again.
C++: 50 Ways to Shoot Yourself in the Foot
C++ is like that Swiss Army knife with 47 tools where 46 of them will cut you if you're not careful. I've been writing C++ for 10 years and I still occasionally segfault because I mixed up std::shared_ptr
and std::unique_ptr
with std::make_shared
and get()
.
The C++26 standard is trying to fix things with contracts and static reflection, but here's the problem: you still have 40 years of dangerous APIs sitting right there. new
and delete
still exist. Raw pointers still exist. malloc
and free
still work. And some legacy library you depend on definitely uses them.
Modern C++ with RAII and smart pointers can be memory safe - in theory. In practice, you'll inevitably interface with some C library that hands you a raw pointer and expects you to call free()
on it. Or you'll work on a legacy codebase where half the code is pre-C++11 and uses raw new
everywhere.
Microsoft found that 70% of their security vulnerabilities come from memory safety issues in C/C++. That's not because Microsoft engineers are bad - it's because C++ makes it easy to make mistakes that don't show up until production.
The Ecosystem Reality Check
Rust's ecosystem is surprisingly mature for a language from 2015. Cargo is hands down the best package manager I've used - cargo add
, cargo build
, cargo test
, it all just works. The 100,000+ crates on crates.io are mostly high quality because the compiler prevents you from publishing memory-unsafe garbage.
But here's what they don't tell you: dependency hell is real. One update can cascade into 47 crate updates and suddenly your build is broken because some transitive dependency decided to rewrite their API. I've spent entire days just getting dependencies to play nice together.
Go's ecosystem is beautiful in its simplicity. The standard library does 80% of what you need. HTTP server, JSON, crypto, networking - it's all there and it doesn't change. Go's compatibility promise means my 2015 Go code still compiles and runs today. Try that with Node.js.
The downside? When you need something outside the standard library, the options are often limited. Want a good ORM? Good luck. The Go community seems allergic to complex abstractions, which is both a blessing and a curse.
Zig's ecosystem is basically nonexistent. You want an HTTP client? Write your own. JSON parser? Roll your dice. The cross-compilation is incredible though - building for ARM from x86 actually works, unlike the C++ nightmare of cross-compilation.
C++'s ecosystem is a 40-year archaeological dig. vcpkg and Conan help, but I've still spent entire weeks just trying to get Boost to link properly on Windows. The upcoming package manager might help, but I've heard that song before.
The memory management choice doesn't just affect performance - it fundamentally shapes how you hire, how fast you ship, and whether you'll be debugging memory corruption when you should be sleeping.