Fighting Rust Anxiety: Insights from a Go Developer, Navigating Rust Syntax Shenanigans
Because who needs simplicity anyway right?
Disclaimer: Rust is an awesome language, and this blog is meant purely for some fun and perhaps to share a bit of frustration!
Today, I bring you a tale of a brave soul who embarked on a treacherous journey into the realm of Rust. You see, I recently decided to trade my cozy Go gig for the enigmatic allure of a Rust job. The promises of blockchain and database nerds who hailed Rust as the supreme language! Where art thou now, you nerds? Show yourselves!
Go, a language with its simple and straightforward syntax, where things just make sense (most of the time, before generics were added). Little did I know that Rust's syntax had a more wicked sense of humor. It left me scratching my head, questioning not only my sanity but the very purpose of my existence as a programmer.
From the moment I laid eyes on those seemingly harmless question marks ?
scattered throughout the code, I knew I was in for some ride. Rust has a way of making even the simplest things feel like unraveling a riddle wrapped in an enigma served on a confusing syntax platter. (You'll master crafting convoluted sentences once you acquaint yourself with Rust). Let’s go through it one by one.
1) The Bermuda Triangle of Syntax
Now, Rust, in all its wisdom, decided to throw traditional return statements out the window. Who needs 'em, right? So, instead of writing something like this in Go:
We have this gem in Rust:
See that? No return
statement. It's like playing hide-and-seek with semicolons. The value of the last expression is returned automatically, whether you like it or not.
And let's not forget my lovely ?
operator. Here's a quick comparison.
In Go, if we want to handle errors, we might write something like:
But in Rust, they thought, "Let's make this more cryptic!" So you end up with:
With just a ?
, Rust takes the award for the Most Mysterious Error Handling. It's as if the Rust designers wanted to make sure we never forgot to ask ourselves, "What is going on here?" Take a look at this another gem of code:
See those question marks sprinkled everywhere? They act as a gentle reminder that any operation could result in failure. Like as if I already did not have an existential mid life crisis.
2) Unwrapping the Error: Options and Results
Speaking of error handling, Rust has two fancy types for this: Option
and Result
.
In Go, you'd typically return an error like this:
However, in Rust, we have Option<T>
and Result<T, E>
. Option<T>
is like a box that might contain a value (Some(value)
) or might contain nothing at all (None
).
Then there's Result<T, E>
, where you can have a successful result (Ok(value)
) or an error (Err(error)
).
In Go, a straightforward error return would suffice, but Rust prefers we juggle Options, Results, and the unwrapping dance. Of course Rust syntax does not lose an opportunity to showcase you its complexity.
3) Generics: The Wild West of Syntax
And then we have generics, where readability goes to play hide-and-seek. Generics, the unsung heroes of code reusability, decide to throw a fiesta in Rust (I disliked it in Go already), leaving me scratching my head in confusion when reading code.
In Rust, generics look like they've been put through a syntax juice blender:
And then it goes one step further. It takes the concept of generics, and adds its eccentric cousins, lifetimes
, and smashes them together, creating a linguistic milkshake. Look at this example:
Did your brain melt? Mine did. Who needs readability when you can have abstract NFT art right?
4) Lifetimes: Because Who Needs Immortality Anyway?
Next up, lifetimes. Now, in the Go universe, you don't need to worry about how long something sticks around in memory.
But in Rust, it wants you to specify how long a reference should be valid. That is completely fine, because Rust is a low level systems programming language. But having syntax like this, is like a frustrating landlord asking you to sign a contract for exactly how long you'll stay every single time.
See those 'a
annotations? Those are lifetimes. Welcome to your rented room for the lifetime of 'a
.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str
: This line declares the functionlongest
with a lifetime parameter'a
. The lifetime'a
is used to indicate that the returned string slice will have the same lifetime as the input string slicesx
andy
. The return type of&'a str
indicates that the returned string slice will have the same lifetime as the input string slices. In other words, it ensures that the returned slice will not outlive the strings it references.
5) Enums: The Infinite Layers of Frustration
Now, enums are not just your regular old enumerated types, like in Go or C. No, sir! In Rust, they are types within types within types. They're like the inception of data types.
Here's a simple enum in Rust:
You see that? We've got unit types, tuple types, struct types, all cozily nested within our enum. A one-stop-shop for all your data needs. And just to add another layer of complexity, we can also define methods on our enums:
Now we can call methods on our enum variants, just like they were regular objects. Isn't that fun? Seems like Rust wanted to take everything from every programming paradigm and stuffed it all into enums.
Let's compare this to Go. How does Go handle enumerated types? It's a bit more simple. In Go, we have constants, and we can group them together using iota
:
It's simple, it's straightforward, and it doesn't involve any nesting of types.
6) Macros: Rust's Magic Spells
Finally, macros. The funny little !
at the end of a function call is Rust's way of saying, "Hey, this isn't a normal function, it's a MACRO"
Let's take a look at an example:
This is a macro to create a vector. It's like a recipe for a vector, but instead of listing ingredients, it lists tokens. You've got your expr
tokens, your tt
tokens, and your ident
tokens, all mixed together with a generous helping hand of parentheses, brackets, and punctuation.
The magic happens when you call this macro:
See that vec!
? That's Rust asking the vec
macro and telling it to turn [1, 2, 3]
into a Vec<u32>
.
Now, what if we want to do this in Go? Well, we don't need any magic spells. We just do it the straightforward way:
No macros, no magic, just simple, straightforward readable code.
And there we have it, that's Rust for you. A language that makes you question your life decisions every time you see a "?" or an enum nesting another enum.
Of course, Rust has its charms. And this blog is meant purely for some fun and perhaps a bit of frustration when learning it the first time. Rust is a systems programming language. It's meant for low-level code. Perhaps when my frustration ends, I will try writing on the enigmatic features of Rust.
To leave you with the better resources which I’ve used to learn Rust:
https://fasterthanli.me/articles/a-half-hour-to-learn-rust
https://doc.rust-lang.org/book/
https://www.youtube.com/@letsgetrusty
Nice and funny one. Feels good coming across your blogs. Great work man.
Nice article