Rewriting it in Rust: First Attempt
Alright, I did it. I jumped on the hype train and I rewrote a side project in Rust. Here's how I felt about Rust as a fresh Rustacean.
Rustup does a great job installing Rust on any given system, and keeps existing installations up to date.
Meh: The Rust Book
The Rust Book, while comprehesive and well written, is mostly a wall of text and theory. Frequently I just want quick answers.
Rust by example often has good examples, but the website isn't indexed or linked to very well.
Nice: Rustlings exercises
If The Rust Book were the only resource available, I probably wouldn't have RIIR-ed. The Rustlings exercises were fast, organized, and even fun, demoing all the essential features in a short amount of time.
Meh: IDE is essential
I don't particularly like being forced to use an IDE. Why wouldn't you want an IDE?! Because IDEs are bloated, opinionated, resource intensive, and distracting. I do use IDEs, but only when I explicitly need to refactor or get some code analysis.
With Rust, type inferrence/detection of implicit types is essential for navigating code. I guess you could explicitly type all your variables, but that appears to be uncommon, plus it would generally just add more effort.
The compiler errors, which are many and constant, are descriptive and often provide recourse. The recourse actions are invaluable!
Struggled: Gap between enums and traits
One would think that a common use case is to require all enum variants to implement a trait, so that you can then execute the trait functions on the enum itself.
It seems enum_dispatch, an external package, is the only way to do that? Which you'd really think would be a built-in feature. Discovering and having to use enum_dispatch caused more than a few hiccups.
Enums also don't have a way to "match shapes" (I guess you could use a macro...).
Nice: Quality default enum variants
Ok Err Some None are first class enum variants, and that feels like a sensible answer to error handling and "null" values.
Struggled: Costly refactors and fighting the compiler
I suspect refactors in Rust are particularly expensive, even more so than other languages.
I started out with some ordinary structs, traits, and enums, but as I expanded on what I had, I ended up fighting the compiler at increased rates. In the end, it was easier to not deal with the custom data structures, delete most of them, and handle data procedurally. (Maybe Linus was onto something). Which leads me to:
I got the impression that Rust was a footgun-averse language, but that doesn't necessarily seem to be the case. In this tiny tiny project I shot my foot by:
- Using custom data structures (structs, enums, and traits) that increased the "attack surface" of compiler errors. For example:
- Trying to make a struct generic when using a trait/enum was simpler
- Making traits that should have been enums
- Expecting enums and their variants to implmement traits (enum_dispatch)
- Writing tests for code that would later get deleted because I got tired of fighting compiler errors
- Making vectors of traits, vs making vectors of ordinary structs, adding overhead cost to the trait
- Fighting compiler errors using Boxes (without really understanding Boxes)
To be fair, shooting your foot is how you learn and grow in any language! But god damn my feet are full of holes.
Struggled: Templating HTML
The state of templating- and the frontend stack in general- is unpleasant. Throwing compiler errors on top of that is extra unpleasant.
This is actually my second time templating in Rust. I've also written a theme for Zola using Tera, which you are currently viewing, and writing that too was comparatively more difficult than other stacks.
Nice: Automatic statics, private by default
It's nice that compiler detects stateful vs non-stateful changes automatically,
fn sample(&self) -> String :
fn sample() -> String ::
Private by default forces you to acknowledge what you expose.
I didn't particularly want to use async, but I guess if you use a third party web server library you pretty much have to.
Introducing async to my app created more than a few headaches. As an example, there can only be one tokio thread, so using a third party library that also uses tokio can causes problems that the compiler doesn't detect.
Nice: That sweet sweet binary
Obtaining a binary is fantastic. No scripting language to deal with, no stack. It just works.
I know that many of the things I struggled with are features, not bugs. Nevertheless, they were still "features" that I struggled with and that others may also struggle with.
It was a fun learning exercise, but I don't feel like a born-again-Rustacean. Rust doesn't appear to be the coding Valhalla that the hype train made it out to be (and I think arewewebyet understates the learning curve a teensy bit).
But I've been able to delete a large portion of the web app's required tech stack and obtain an easily-shippable binary, which is quite nice.