esc
Anthology / Yagnipedia / Rust

Rust

Assembly Language for People Who Think They Want Assembly Language
Technology · First observed 2010 (Graydon Hoare, Mozilla Research) · Severity: Philosophical (the pain is the point)

Rust is a systems programming language that guarantees memory safety without garbage collection, achieves fearless concurrency through an ownership model, and ensures that no program will ever contain a use-after-free bug, a data race, or a developer who has slept.

The language was created by Graydon Hoare at Mozilla Research in 2010, out of the reasonable observation that C and C++ allow programmers to make catastrophic memory errors, and the equally reasonable conclusion that the compiler should prevent these errors at compile time rather than discovering them at 3 AM in production. This reasoning is impeccable. The resulting developer experience is what happens when impeccable reasoning meets human patience.

Rust is, by every objective measure, a better language than Go. Its type system is more expressive. Its error handling is more elegant. Its performance is closer to the metal. Its safety guarantees are mathematically proven. Its concurrency model prevents entire categories of bugs that Go programmers discover only in production.

Rust is also the language in which a senior developer, ten years into their career, will spend an afternoon fighting the borrow checker over who owns a string. The string does not care. The borrow checker does not negotiate. The afternoon is gone.

“Go chose visibility. Go chose boring. Go chose right.”
Go

The Ownership Model

Rust’s central innovation is the ownership model: every value has exactly one owner, the value is dropped when the owner goes out of scope, and you may have either one mutable reference or any number of immutable references, but never both.

This is brilliantly simple. It is also the reason that a linked list — the data structure taught in week two of every computer science programme — requires unsafe blocks in Rust, because a doubly-linked list has nodes that are owned by… well. By the list. But also referenced by their neighbours. But the neighbours don’t own them. But they need mutable access. But you can’t have mutable access while immutable references exist. But the iterator needs immutable references. But insertion needs mutable access. But—

The Rust community has produced a document titled “Learning Rust With Entirely Too Many Linked Lists” which is 50,000 words long. The document is not a joke. The document is a survival guide. The linked list is the Rust community’s Vietnam: everyone has a story, nobody came back the same, and the official recommendation is “use Vec instead,” which is correct, practical, and completely beside the point for anyone trying to understand what they signed up for.

In Go, a linked list is a struct with a pointer. The pointer can be nil. This will cause a panic at runtime. The panic will occur at 3 AM. The developer will fix it in ten minutes. In Rust, the linked list will not compile until the ownership semantics are correct. The developer will spend three days making them correct. The program will never panic at 3 AM. The developer will have lost three days that could have been spent shipping features that users actually wanted.

Both languages made a choice. Rust chose correctness. Go chose shipping.

The Async Disaster

If the ownership model is Rust’s monastery, async is the monastery on fire.

Asynchronous programming in Rust requires understanding: Future, async, await, Pin, Unpin, Send, Sync, 'static, Box<dyn Future>, tokio, async-std (but not both), executors, runtimes, spawn, spawn_blocking, block_on (but never inside an async context), and the sentence Pin<Box<dyn Future<Output = Result<T, Box<dyn Error + Send + Sync>>> + Send + 'static>>, which is a real type signature that a real developer must write, read, and debug to make an HTTP request.

In Go:

go func() {
    resp, err := http.Get(url)
    // done
}()

The go keyword launches a goroutine. The goroutine runs concurrently. The scheduler manages it. There is no Pin. There is no Box<dyn Future>. There is no existential question about whether the future is Send. There is a function that runs, and a two-letter keyword that makes it concurrent.

In Rust, the equivalent requires choosing an async runtime (tokio or async-std, a schism that has divided the community like vim and emacs but with more Pin<Box<dyn>>), annotating the function as async, annotating main as async, adding #[tokio::main] as a macro attribute, and then discovering that the closure you wrote captures a reference that doesn’t live long enough because the future might be moved to another thread and the borrow checker cannot prove that the reference will outlive the future because the future’s lifetime is 'static because it must be Send because tokio::spawn requires Send because—

The developer adds .clone(). The program compiles. Nobody speaks of what was lost.

The Rust async ecosystem is what happens when a language with a precise ownership model meets a programming paradigm that is fundamentally about sharing state across time. Ownership says: one owner. Async says: many tasks, shared resources, unpredictable lifetimes. The intersection of these two philosophies is Pin<Box<dyn Future<Output = Result<T, Box<dyn Error + Send + Sync>>> + Send + 'static>>, and if you think that type signature is readable, you have been in the monastery too long.

Assembly Without the Fun

There is a particular irony in Rust’s position in the language ecosystem. Rust is often recommended as a replacement for C and C++ — languages in which the developer has direct control over memory, direct access to hardware, and the exhilarating/terrifying knowledge that every pointer arithmetic operation could corrupt the heap.

C programmers accepted this bargain. They accepted it because they could see the machine. The assembly was right there. The memory layout was knowable. The pointer was an address. The address was a number. The number corresponded to a physical location in hardware. The mental model was simple, concrete, and occasionally fatal.

Rust offers safety. Rust eliminates the fatal part. But it also eliminates the seeing. The borrow checker operates on an abstract model of ownership that does not map to how the hardware works — it maps to how a type-theoretic proof works. The developer is no longer thinking about memory. The developer is thinking about lifetimes, which are annotations that describe the relationship between references, which are themselves abstractions over pointers, which are themselves abstractions over addresses.

The assembly programmer thought in machine cycles. The C programmer thought in pointers. The Rust programmer thinks in lifetime annotations. Each step is more abstract, more correct, and further from the metal that systems programming was supposed to be about.

Rust is assembly language for people who wanted the control of assembly but didn’t want the risk. What they got was the cognitive load of assembly — arguably more — without the intimate knowledge of what the machine is actually doing. The fun of assembly was the directness: you told the machine what to do, and it did it, and if it crashed, you knew exactly why. The experience of Rust is telling the compiler what you want to do, and the compiler telling you why you can’t, and eventually discovering that what you wanted was impossible under the ownership model and you need to restructure your entire approach.

The assembly programmer debugged at 3 AM and fixed it in ten minutes. The Rust programmer cannot create the bug that requires 3 AM debugging. The Rust programmer instead spends the afternoon that would have been 3 AM debugging on fighting the borrow checker at 2 PM, which is safer, correct, and — the assembly programmer would note — considerably less fun.

The Squirrel’s Paradise

The Caffeinated Squirrel loves Rust with an intensity that concerns medical professionals.

The type system is expressive. The error handling (Result<T, E> with the ? operator) is elegant. The pattern matching is comprehensive. The trait system is powerful. The lifetime annotations are — and here the Squirrel’s eyes take on a particular gleam that veterinarians associate with either enlightenment or a specific nutritional deficiency — beautiful.

The Squirrel proposed rewriting the backend in Rust approximately once per quarter. The proposal included benchmarks showing Rust outperforming Go by 15% on synthetic workloads that bore no resemblance to the actual application, which serves twelve hundred users from a SQLite database and is bottlenecked by the speed of the human reading the response, not the speed of the server generating it.

“Where’s the AbstractStorageProviderFactory?”
“You’re looking at it.”
— The Squirrel, moments before proposing a trait-based generic storage provider with lifetime annotations that would have been the AbstractStorageProviderFactory’s spiritual successor but in a language that compiles to native code, The Databases We Didn’t Build

The Lizard did not respond to the Rust proposals. The Lizard was asleep on a compiled Go binary. The binary had been running for six months. It had never been restarted. It was still serving requests in fifty milliseconds.

The Ferris Paradox

The Rust community is one of the most welcoming, helpful, and technically rigorous communities in software. The documentation is excellent. The book (The Rust Programming Language) is genuinely well-written. The error messages are the best in any compiled language — they tell you what went wrong, why it went wrong, and what to do about it.

And yet.

The Stack Overflow answer for “how do I make an async HTTP request in Rust” is longer than the Book of Genesis. The answer is correct. The answer is helpful. The answer requires understanding ownership, borrowing, lifetimes, trait bounds, generic constraints, future combinators, and the Pin API. The equivalent Go answer is four lines of code and a link to the standard library documentation.

This is the Ferris Paradox: a language with the best documentation, the most helpful community, and the most informative error messages — that still requires more documentation, more community help, and more error message reading than any other language in mainstream use. The quality of the support infrastructure is a direct function of how much support is needed. A language that requires a 50,000-word guide to implement a linked list has earned its excellent documentation.

The Correct Choice

Rust is the correct choice for:

Rust is not the correct choice for:

The tragedy of Rust is not that it exists — it is magnificent, and there are domains where nothing else will do. The tragedy is that Rust is recommended for everything, by enthusiasts who have confused “the safest tool” with “the right tool,” in the same way that the Design Patterns enthusiasts confused “a vocabulary for describing code” with “a menu for ordering code.”

A scalpel is the correct tool for surgery. A scalpel is not the correct tool for buttering toast. The toast does not benefit from surgical precision. The toast benefits from being buttered quickly and eaten while it’s warm.

Go is a butter knife. It is not elegant. It is not precise. It is boring, and fast, and the toast is ready.

Measured Characteristics

                                Go              Rust
"Hello World" compilation:     0.2 seconds      1.4 seconds
HTTP server (stdlib):          15 lines         42 lines + tokio
Linked list:                   1 struct         50,000-word guide
Async HTTP request:            4 lines          see: Book of Genesis
Learning curve:                weekend          semester
Error handling:                if err != nil    Result<T, E> + ? (elegant)
Deployment:                    scp              scp (fair: same)
Binary size:                   12 MB            8 MB (fair: smaller)
Memory safety:                 GC (runtime)     ownership (compile-time)
3 AM production bugs:          occasionally     never
3 PM borrow checker fights:    never            daily
Developer happiness:           quiet            intense (in both directions)
Time to first production:      days             weeks
Conference talks generated:    "Go is boring"   "Fearless Concurrency"
Squirrel approval rating:      12%              97%
Lizard approval rating:        97%              blink

See Also