Thoughts on Rust after six months

Disclaimer

I’m writing this post as an honest and frank description of my experience coding in Rust these last six months. It’s just, like, my opinion, man. Maybe you would have a completely different experience. Maybe I’m doing it all wrong and should be regarded with a blend of pity and contempt. Maybe I’m a Russian troll sowing discord as part of some inscrutable geopolitical long game. I leave it to you to decide for yourself.

Background

First off I should admit this is not my first time with Rust. I tried Rust back in August 2015, and I did so in a public repo so you can see for yourself the ensuing debacle. My final commit message in that repo was thus:

Came to the conclusion that Rust’s ownership model is too restrictive to be enjoyable to use (see src/wtf.rs)

I don’t remember what the specific problem was, but I know Rust was a different language back then, and I was certainly a different programmer. I can see from the code I wrote then that I was not making enough effort to understand the mechanics of the borrow checker, and thus I wrote non-idiomatic Rust code and fought constantly against the compiler. I know many other programmers have had this same experience, and I think anyone who approaches Rust expecting to write C++ or Java-like code without understanding Rust idioms is bound to have a bad time.

The tl;dr is: Rust is a very poor language for anyone who wants to write (C++/Java/etc) code. If you want to program in Rust, you need to write Rust code. Tautology? You’d be surprised…

Present attempt

For the last six months or so I’ve been working on a few Rust projects. None of them are in public repos yet, but they will all eventually be open sourced under the dual MIT/Apache 2.0 license favored by the Rust community. The details of my projects aren’t particularly important, but only to say that they are non-trivial libraries in which high (and consistent) performance and concurrency are critical. Given Rust’s reputation it should be a perfect fit.

How not to learn Rust

I alluded above to the fact that if one wants to learn Rust, one must be open to a very different way of approaching some problems. I can virtually guarantee that anyone with prior programming experience in any modern statically typed language will initially find themselves fighting the Rust compiler for every inch of ground. It will be frustrating, and not at all productive.

I think noted Rust programmer and author George Bernard Shaw said it best:

I learned long ago, never to wrestle with the Rust compiler. You get dirty, and besides, the compiler likes it.

  • George Bernard Shaw

How to learn Rust

My advice to anyone who is genuinely curious about Rust and genuinely interested in trying it out, is to approach it with humility and an open mind. First read the book, and then try to write something of your own. If you’re anything like me, you’ll fail in a cascade of messages about missing lifetimes and types without a size and traits not being objects. It can be frustrating at times, but you should try to cultivate the right mindset: many smart and experienced programmers enjoy using Rust every day, they can’t all be masochistic imbeciles, maybe there’s something I’m missing, and it will be interesting to figure out what it is.

I should also point out that the Rust community on Stack Overflow is very active and very helpful. If you get stuck they can help, though most likely someone else already asked the exact same question and the help is only a search away.

My only complaint about the available resources for learning Rust is that they are very poorly SEO’d. I very often get results from older editions of The Book, from mailing lists and RFCs that are ancient and no longer reflect modern Rust, or even worse RFCs or GitHub issues discussing some cool new future feature of Rust that isn’t ready yet and may never land. Though I’m an avid user of DDG, I found myself having to prefix all my Rust searches with !g because Google seemed to do a much better job of pulling relevant results.

How to fight the compiler

The only way to get smarter is by playing a smarter opponent. The Rust compiler is a smarter opponent.

  • Fundamentals of Chess Rust Programming 1883

Rust reminds me a lot of Scala in that the compiler is fanatically pedantic, seemingly incapable of the slightest bit of imagination. If anything Rust is worse in this regard, due to the incorporation of lifetimes into the type system. Over the last six months I’ve come to expect a mighty struggle for every successful build, and in fact I’ve come to relish it.

I can’t emphasize enough the importance of mindset, particularly early on. The Rust compiler is an incredible achievement, not only in the use of borrow checking for provably-correct elimination of an entire category of bugs, but even more so for the astonishing helpfulness of most of its error messages. While it’s sometimes frustrating trying to understand how to fix some of the errors it reports, I’m consistently amazed by the humane quality of the compiler output. Take advantage of this achievement and learn from the compiler. As I gained experience with its particular brand of pedantry, I found myself anticipating its objections, and structuring my code differently. Not just differently; better. More idiomatically Rust, yes, but also, subjectively, better code.

I heard this time and again from other programmers new to Rust: the Rust compiler forced me to become a better programmer. This was my experience as well. I’ve lost count of how many times I’ve written code that was obviously correct, only to have it soundly rejected by the compiler. In the ensuing fusillade of profanity in which the virtue of the compiler’s mother is rudely impugned, I’d then come to the humble realization that it’s correct, and that what I was trying to do has some subtle edge case or failure mode I didn’t see. After I’d meekly change the code or add an explicit lifetime or change the scope or whatever I need to do, I’d be forced to admit the result is simply better code.

rustfmt is fantastic

For most of my programming career, I’ve been fanatical fairly opinionated about code style. I long wished there were some tool that could be run on (other people’s) code so that it would look right like my code. I tried a few tools over the years but I always got bogged down in litigating specific details of this style or that style and never actually got anything done.

A few years ago, while coming to the conclusion that Go is a language that simply rubs me the wrong way, I had occassion to use gofmt. Easily the best feature of the Go ecosystem. There are no configuration options, no ability for your team to bikeshed or litigate on which indentation style is best. There’s only one way, and it’s the gofmt way. That imperious arrogance is part of why I really, really came to dislike Go, but I won’t hesitate to give praise where it’s warranted: gofmt is the right answer.

Thankfully there’s rustfmt for Rust and it’s similarly great. Though it has more configurability than gofmt, it seems to be generally accepted within the Rust community that the defaults are the right answer. This is even built in to the official Rust plugin for Vim, so rustfmt formatting can be automatically applied on save. Because rustfmt must first run the code through a much less thorough and complete version of a compilation cycle, I’ve noticed that even without looking at the compiler output I can tell when there’s an error somewhere if on save my code isn’t slightly reformatted by rustfmt.

Seriously, be careful with rustfmt. If you use it for a little while and then have to go back to a language without a universally accepted beautifier, you are likely to seriously regret some of your life choices…

clippy is great too

clippy is a linter for Rust. While the rustc compiler compiles the code, and rustfmt handles formatting, clippy analyzes working, compiled code for all manner of possible improvements. It’s not on be default, but I strongly suggest you install it and configure it to fail the build on any violations of the checks that are turned on by default. I haven’t experimented with turning on the more pedantic checks, because I’m sure they’re called “pedantic” for a reason.

The great thing about clippy for a beginner is it finds and points out all sorts of small mistakes that you otherwise might not notice. For example, maybe you are using unwrap_or when unwrap_or_else would be sufficient, or you tried to Box a Vec which is just a wasted allocation. As hard as it is to write Rust code that will actually compile, there’s still plenty of room for mistakes that get past the compiler only to be caught by clippy.

cargo and rustup make dependencies effortless

Though I don’t intend to write a post bashing Go, I can’t help but take another swipe here. Go’s dependency management story was absurdly awful when I tried it (in fairness that was years ago maybe it sucks less now). So bad that the highest praise I could think of at the time was “it’s better than C++”. Ouch.

I mention this only by way of contrast to Rust’s dependency story. It is honestly not much different in ergonomics than npm for Javascript or bundler for Ruby. Well, except that Rust is a strongly-typed, compiled, low-overhead systems language! It’s such a joy to find the crate you want, add it to Cargo.toml, then cargo just does what it needs to do. “What it needs to do” usually means resolving a long dependency graph, downloading the code for all the new transitive dependencies, compiling them, and resolving your use statements to the right libraries. Honestly it’s still impressive to me how well cargo works. I have very quickly come to take it for granted.

Coming as I did most recently from Scala, I can compare cargo most directly to sbt. There is no comparison. sbt is a radioactive dumpster fire of pathological over-engineering (does the s still stand for “simple”??), and the Maven/Ivy dependency scheme is…well, it’s exactly what one expects of the Java ecosystem, I can’t think of harsher criticism than that. cargo is a breath of fresh air, after which I finally realized just how much I hate working with sbt.

Don’t be afraid of nightly

Though Rust is surprisingly mature for such a young language, it’s still a very quickly moving target. There’s a new stable release seemingly every week, though in fact I think it’s more like every six or eight weeks. Getting the new hotness requires only a rustup update, and in the stable channel nothing has ever broken for me after an update. In 2018 the new “2018 edition” of Rust was released, which was the first edition since “2015”. The 2018 edition added some new functionality that wasn’t entirely backwards-compatible, but a tool called rustfix handled the changes automatically for me.

That said, the Rust nightly channel is an entirely different beast. As the name implies, it is built nightly, and is where all of the new, experimental, unstable, and also the most cool features live. In my case, I’m writing a lot of async I/O code for which the forthcoming async/await syntax is critical. This is not stable, it’s only available in nightly, which means I have to build my code against nightly rust. This isn’t ideal, but nor is it a deal breaker. It just requires a bit more understanding of what is going on. For someone just learning Rust my advice is to stay on stable, but if you run into something that requires nightly don’t automatically assume it’s too unstable and broken for you to work with. It’s easy enough to rustup install nightly, and equally easy to go back to the warm maternal embrace of stable if nightly is too dark and full of terrors for you.

All the cool kids are doing it

I’m not one to be a slave to fashion, however I’m surprised how often I’ll start using a tool only to discover that it’s written in Rust. Here’s a partial list of fantastic open-source tools built in Rust:

  • alacritty - My favorite terminal emulator
  • ripgrep - grep alternative that’s absurdly fast
  • bat - A smarter faster cat that does syntax highlighting
  • fd - Stupidly fast fd goes great with fzf
  • actix-web - A Rust web app framework with very impressive TechEmpower benchmark results
  • LanguageClient-neovim - The Vim plugin I use to add LSP support to Vim
  • stratisd - RedHat’s new storage management system for RHEL, replacing Btrfs with something that presumably won’t eat your data

Summary

How would I summarize my experience with Rust six months in? Thusly: :heart:

Don’t get me wrong, Rust still has rough edges. I often run into something that doesn’t work, for which the answer is “oh yeah that should work, once (INSERT RFC HERE) lands, which BTW might not ever happen but in any case won’t be soon”. Sometimes the compiler’s pedantry still aggravates me. Sometimes I find the unit testing support to be primitive. And it’s definitely not the language which allows for the fastest prototyping of ideas, though it’s definitely a contender for the language which produces the fastest production-ready implementation.

But all languages have their limitations. After venting one’s frustration about some limitation or bug or design decision, the important question to ask, and to answer, is “would I rather be using another language”? For me, now, the answer to that most important question is always “no!”.

In fact, I haven’t enjoyed working in a language this much since I started to play with Scala back around 2012. I expect that Rust’s popularity will only continue to grow, and that more members of Go projects will look longingly at Rust projects and wonder what might have been…