After one year of Rust
Well, it’s been months since I last wrote, but time flies 🕊️
So here is my little journal, mostly a collection of thoughts, after one year doing microservices web development in Rust.
Learning curve
I’ve sometimes heard that it takes one 1 month to get productive with Rust.
Well, I’m not computer science graduated (my major was more focused on project management actually), but still I’ve been in this field for over 13 years and I wouldn’t quite agree on this. Maybe it comes more easily to former system programmers, or maybe some people are faster-learner than others.
As a mere comparison: as far as I remember JavaScript took me 1 month to get very comfortable with, and under 6 months to be productive with for any task to code.
Rust is … different.
And the context in which I learned it was different too : my previous and only knowledge of Rust dated back early 2019, when I finally read the Rust book thoroughly. I experimented, very briefly, to setup basic client requests to websocket. Saying that I understood all the concepts there, or even that I remembered them in 2020 would be a blatant lie. Freshly arrived on my new contract, I was supposed to be trained with Rust. Turned out, as it often does, that I had to learn on my own while still having to deliver features within deadlines. Reasonable ones though, thankfully, which gave me enough time to improve, but not enough to properly read through the docs, watch learning resources, experimenting and so on. Mostly this would have to be done on my free time, which I did extensively. And it was an exhausting year to be honest.
I would say that after first month I was able to write Rust code without spending every 2 minutes or so gobsmacked at compiler’s errors. I would already be accustomed to most of them by then, and to the basics. Be familiar with compiler’s error is necessary: it means you understand what the compiler wants from you.
Somehow long and extensive traits would still leave me dumbstruck though, for example this one:
So what is this ? You could call it an “interface” in another language.
It describes a middleware here, which will enforce a timeout on its requests.
It accepts a generic request and will return, at some point in the future (which is to say, asynchronously) either a generic response, or a generic error.What is interesting here, first, is that this Timeout will automatically be implemented for any Type in your library, the standard library or any library that is or will ever be created in Rust one day, as long as this code is in scope.
Notice this mischievous T::Error ? It requires anything that can be turned Into a pointer to a dynamically-sized Error (whose size is not known at compile-time, basically allowing you to pass any kind of error freely) Boxed somewhere on the heap (oh and this pointer is also guaranteed to be non-null by the way, but this is another topic). This “error” is also required to be safe to both Send and Sync (or share, if you prefer) between threads (so that the runtime can correctly schedule the task over multiple threads, if deemed appropriate). Both response variants have to live in memory long enough for the asynchronous call to complete, which is what ‘static indicates in this context.
Rust language is indeed overly expressive.
But once you start to understand it, it’s beautiful.
After 3 months I was finally familiar with them, and writing custom implementations was sometimes tedious but eventually done.
I also jump on the opportunity to thank the Rust community at large, especially tokio and tonic contributors and maintainers. They always went out of their way to provide hints and point me in the right direction kindly. No matter how dumb my questions were, at times.
As a side note to my own story, Rust has “many facets” if I can say. One of them is meta-programming, or macros as they are called. It’s basically code that allows you to generate code. I started playing with them very late, mostly lacking time beforehand, and realized that it could have spared me tedious and time-consuming boilerplate.
Which is an indicator, among others, that I also “mislearned” Rust at times, and realized way later that it could actually have been done way easier. So my take on this would be : Rust is best learned with proper amount of time and reflexion. Typically I wouldn’t recommend following the same path as mine, except if you like challenges.
I would say that, after that, most of the actual learning is the ecosystem itself. Being a very expressive language, Rust crates authors design “ways” to use their code and sometimes it comes naturally, sometimes it takes a while to get used to them. But the more you get to know them, the more you understand why they were designed the way they are.
Time has passed and what I want to share eventually, is that Rust requires proper time, reflexion and shift in mindset. There are some deep benefits that you won’t even see before a couple of months, and this is absolutely normal. There’s no magical fast-forward learning button. For seasoned developers, it can even be more difficult than newcomers, since we all bring along former habits from former languages (in my case ActionScript, PHP, Java and JavaScript / TypeScript).
A notable difference though, lies in available learning resources: when I started, tutorials and documentation were oftentimes scarce, to say the least. As of now, I see more and more content available to newcomers, more accurate documentation and books, literally new tutorials popping every week.
And that’s a pretty good thing.
Is this worth your time ?
Spoiler alert: it is, indeed 😉
Let’s see why!
Write once, build everywhere ⚙️
We’ve heard this before, right ?
But this time maybe for real.
Sure I’ve struggled in the beginning and I’m still cognitively exhausted at times with Rust but in the end, it has never disappointed me: past the compiler requirements, in Rust there’s never a “you cannot do that” moment.
Ecosystem might not be mature yet in this particular use case of yours and it might be hard, overwhelming, or simply premature ? Sure. It will require you tremendous amount of reading and experimentations ? Definitely.
But in the end everything can be done.
Simply because Rust compiles to native code on a wide range of platforms and has first-class support for FFI, which means it can talk to most if not any other languages.
I know it can help me build literally anything, provided time and effort.
Setup a whole server infrastructure ? Of course.
Write a custom Flutter plugin, a native Nodejs module or whatnot ? Yes.
Even build a video game, an embedded device … you name it!
Probably even build a connected toaster sent to the moon, who knows.
Compile-time errors & safety ☔
Simply because, by default there’s a wide range of errors that simply cannot exist at all in Rust.
One of its core pillars is the ownership & borrowing model: compiler basically enforces at any time that your variables are correctly assigned, passed along in functions, referenced and consumed.
It is very subtle in the details, but very simple in essence: you can only mutate a variable in one place at a single time, or read from multiple places at a time, but never ever do both at the same time.
And because of that, memory can be automatically deallocated when variables go out of scope, without any need for a garbage collector.
Gone also are the dangling pointers, use-after-free, double-free, null pointers exceptions, and so on.
In slightly over a year of Rust, I got very few runtime errors on my web services. So few distinct ones that I probably can count them on my fingers, seriously.
Once it compiles, usually it just works™.
Performances akin to C & C++ ⚡
Simply put: it is ridiculously fast.
It means that for example, coming from Nodejs, in Rust even if I over-allocate memory (clone it till you make it®) or write under-optimized code, it will still be on average from 5 to 20 times faster than carefully crafted and optimized JavaScript code. For a way lower memory consumption.
Small anecdote: very first time I looked at data deserializing throughput (with serde) I thought I made a typo in console output, because my input was a big chunk of data and results were displayed … in nanoseconds. I checked and ran it over and over until I realized everything was fine. That’s how fast it is.
“Who goes there? Friend or foe?” asked the sentry 🎭
And the compiler ? It can feel annoying in the first weeks.
It definitely feel like an impediment for a few months (to one’s own productivity I would say). But eventually becomes a best friend of some sort.
Compiler errors are overly descriptive, to such an extend that it often offers direct copy and paste solutions, with explanations.
Whenever I’m tired and omit something, or do things in an improper way:
the compiler is there to diligently reminds me.
Also because of the strict nature of Rust, I should mention that I now spend way more time writing tests that assert actual business logic, not tests that assert my assumptions about how the language behaves.
In other language we, as developer, actually hold invariants by ourselves. Having to remember that such method doesn’t properly work in such context, remember that such variable type behaves wildly when compared with another type, that kind of things. In Rust everything is boringly typed. There’s simply no other way than to get it correct, by default.
It tickles your curiosity ?
Give a shot at this excellent article of Amos for deeper explanation👇
Serenity in chaos 🤸♂️
Refactoring is a bliss. Literally a piece of cake 🍰.
Most of the time it’s just merely following compiler’s instructions.
Since most of the errors happen at compile time in Rust, it’s very unlikely that anything breaks or regresses once it compiles.
And I cannot quite hold the same affirmation for other languages that I’ve worked with. Do I even know what I’m talking about ? Just ask my former teammates who is “RefactoMan”.
A craftsman is nothing without his tools 🧰
Tooling is also where Rust feels cozy, productive and practical: for example, unit-tests, documentation generation, code formatter and linter are built-in.
It’s just here by default.
And there’s much, much more tools at one’s disposal.
For examples:
- see generated assembly code for your program ? 👉 cargo-asm
- fuzz your tests ? 👉 cargo-fuzz
- timely compare 2 scripts execution ? 👉 hyperfine
- profile your code ? 👉 flamegraph
- enforce valid SQL queries at compile time ? 👉 sqlx
- enforce valid HTML template at compile time ? 👉 yarte
- use advanced tracing and telemetry ? 👉 opentelemetry
etc …
A switch in mindset 💭
More than that, I personally feel like that I have learnt more about computer science this year than ever before. Not particularly that it was unavailable or under-documented before. It always was at my arm’s reach. It’s just because, in most other languages, low-level details are abstracted away so there’s no real incentive to dig deeper.
Rust’s upside of having so many requirements to fulfill to get one’s code to compile is that it pushes you to revise your basics. Things are not taken care of by wonderful V8 black-box anymore.
Well, let me rephrase a bit here for clarity: Rust doesn’t force you to know each and every single underlying details, but it makes you very aware of them.
Also, everything that you will learn about memory safety on your journey in Rust is language-agnostic: so it will be useful with all other languages that you do afterwards.
Push the limits ⚗️
What I particularly enjoy is the effervescence around Rust.
Many people are revisiting or innovating in domains that would have been previously arduous, if not impossible, to get done correctly in other languages.
Given the compiler won’t ever let you compile code which isn’t memory-safe, it provides a “safety net” context to experiment crazy stuff (especially multi-threaded ones).
A good example among others would be rayon, a crate which allows you to parallelize iterations of CPU intensive task by simply changing one line of code.
Fast growth 🪴
Think about it for 1 second:
9 years ago: it got incubated by Mozilla Research.
7 years ago: it reached first stable release.
4 years ago: it officially started supporting async-await syntax.
Today: it already provides fully fledged production-ready servers solutions.
Well, I don’t know about you but I can’t remember of a programming language ecosystem growing so fast, except maybe JavaScript back in the days.
Funny fact is that you might actually already use some without even being aware of it. For example, did you know that some Rust already made its way into: Dropbox, Figma, NPM, Microsoft, CloudFlare, Facebook, Amazon, Discord, and Linux kernel soon ?
Ecosystem has still plenty of areas to improve though:
This Week in Rust and crates.io are good means to take the temperature.
Oh, and there are websites keeping track of its evolution, usually labelled like: arewegameyet, areweguiyet, arewewebyet, etc.
That can good give you an idea if Rust is ready for your next project, or requires some extra maturity.
Productivity 🏃
So generally speaking, the way I would summarize it as a whole:
is it worth spending time upfront ?
Especially in the first months, doing day-to-day tasks in Rust can take up to thrice the time one would do them usually. The gap eventually narrows down as one improve too: now for example I can setup a fully fledged REST API with Rust as fast as I did with Nodejs. It just took longer to get there.
It’s basically trading time upfront at compile-time,
that you eventually save on debugging at runtime.
And I personally prefer this methodology by far.
Remember that tiny little grinny bug that caused your script to fail erratically ? Remember the time you spent debugging it ? For days, weeks maybe ?
Ask yourself if you would prefer to invest more time upfront instead.
That’s also what Rust is all about.
So let’s summarize my takeaways
to someone who would like to start Rust today:
- do not rush: learn at your own pace, take proper time and reflexion.
- leave former habits at the door, refrain from systematically comparing with other languages: it requires a shift in mindset.
- do not fight the compiler: embrace its advices as early as possible.
- do not try to over-optimize early: write something that compiles first, and improve its efficiency later on.
- start small, grow over time: find tutorials on topics that you like that can get you going quick, and read the Rust book along your journey.
- do not make your life complicated: a.k.a using it without prior experience for a client with big expectations and short deadlines.
- have fun with it, experiment!
Closing words 💬
Since you made it till the end,
thank you for your time, I hope you enjoyed this read 🙂
I actually have a couple of articles on actual Rust programming in mind,
that I hope to deliver soon.
Until next time, take care y’all!