Rust Reflections
This blog currently runs on Rust. I wanted to reflect on my Rust experience so far. I find Rust's value proposition compelling: it offers a systems programming language that guarantees memory safety and type safety while maintaining performance parity with C and C++. Crucially, it achieves this while successfully integrating modern paradigms—such as functional programming, immutability by default, and closures—into a low-level context. The selling point is: System Programmers can have good things!
The Language Landscape
Programming languages are typically categorized into three distinct buckets:
- Scripting: Python, Ruby, and Perl offer rapid iteration and flexibility at the cost of runtime performance. These languages use garbage collection (GC) for memory management.
- Managed Memory (GC): Languages like Go, Java, and C# excel in developer velocity with more than adequate performance. They rely on garbage collection for memory management.
- Systems/Native: C and C++ provide maximum control and zero-cost abstractions but require manual memory management, introducing a class of safety vulnerabilities.
Rust distinguishes itself by bridging these worlds, attempting to bring high-level abstractions to systems programming without manual memory management.
The Alan Perlis Heuristic
While learning a new language, I often recall Alan Perlis's quote:
"A language that doesn't affect the way you think about programming is not worth knowing."
Rust emphatically passes the Perlis filter.
The Learning Curve
My initial approach to Rust was theoretical, relying on a few key texts [1] [2] [3] to grasp the high-level concepts. As someone coming from a background currently heavy in Go—which prioritizes simplicity in syntax and semantics—Rust presented a stark contrast.
It is undeniably a dense language with a steep initial learning curve. However, I found that it rewards incremental exploration. At a minimum, one must become familiar with the core concepts of ownership, borrowing, and lifetimes, but advanced features can be adopted on demand as you explore more.
The compiler also acts as a reliable partner, auditing the code to ensure there are no obvious memory leaks. When working on large codebases, this is a significant advantage. "Fearless refactoring" is a common theme in the Rust ecosystem.
Getting Hands Dirty
To bridge the gap between theory and practice, I began by building a "mini-web framework" from scratch. Using crates like tokio, hyper, and tower, I constrained the implementation to a single file of approximately 600 LOC.
Recently, I decided to rewrite a static site generator (SSG) I had previously written in Go. The architectural goal was straightforward:
- Ingest markdown files from a directory.
- Convert markdown to HTML.
- Serve the blog via an in-memory cache.
The resulting Rust implementation proved to be more concise than its Go variant. Currently, this blog is powered by Rust.
Go vs Rust
Both are great languages. I have always enjoyed working in Go. The language is simple, performant, and has that get-things-done vibe to it, though the code can sometimes get verbose. In Go, the standard library provides a lot of features out of the box, such as a web stack and an async runtime.
In Rust, one has to exercise more cognitive effort upfront; it has that compile-it-forget-it vibe to it. The Rust standard library is relatively minimal; typically, one needs to import many more dependencies.
A common pattern I have seen is that some teams use both languages: Go for the control plane and Rust for the data plane.
Cross-Compiling for Linux: Docker vs. Zigbuild
While setting up this blog (which runs on Rust), I needed to build binaries for the Linux target environment from my local machine.
I had two main choices for cross-compilation:
- Docker
- Zigbuild (
cargo-zigbuild)
I found zigbuild to be the much simpler option. It didn't require spinning up a container engine and worked seamlessly with my existing tools.
Sequence of steps involved:
1. brew install zig
2. cargo install cargo-zigbuild
3. rustup target add x86_64-unknown-linux-musl
4. cargo zigbuild --target x86_64-unknown-linux-musl --release
Conclusion
Once you overcome the initial learning hurdle, you slowly begin to appreciate the language's merits. Rust attempts to solve as many problems as possible at compile time, so initial development can be slightly slower. However, the gains are exceptional performance, stability, and efficient resource utilization.
There are moments when the syntax can become complex and dense, resulting in code that requires multiple readings to fully grasp the semantics. Since code is read far more often than it is written, we should always aim for simplicity and comprehensibility.
And because code is read far more often than it’s written, Rust is at its best when we keep it simple, explicit, and easy to understand.