Ramblings of an aging IT geek
← Ramblings of an aging IT geek
rust

the rust i write now started with the 2018 edition

Looking back at how the 2018 edition reshaped my Rust, from the module system that finally made sense to non-lexical lifetimes quietly removing a class of borrow-checker fights.

A code editor with Rust on screen

I learned Rust before the 2018 edition and I fought it the whole way. Not the borrow checker, or not only the borrow checker. The module system. I never quite knew where to put a mod or when I needed extern crate or why use paths sometimes worked from the crate root and sometimes didn't. I copied incantations from other people's code and hoped. It worked, but I didn't understand it, and that's a horrible way to write anything.

The 2018 edition is the point where Rust stopped feeling like a language I was tolerating and became one I actually enjoyed. A few changes did most of the work.

The module path overhaul was the big one for me. extern crate mostly went away. Paths started with a crate name or crate, self, super, and suddenly the rule was something I could say out loud instead of pattern-match by trial and error. I went from avoiding modules, dumping everything in one file because splitting it up always broke imports, to splitting things up freely because the imports just worked.

Non-lexical lifetimes were the quiet hero. Before NLL, a borrow lasted until the end of its lexical scope whether you used it again or not, so this kind of thing was a fight:

let mut v = vec![1, 2, 3];
let first = &v[0];
println!("{first}");
v.push(4); // pre-NLL: rejected, the borrow "lives" too long

After NLL the compiler understands that first is done with after the println!, and the push is fine. A whole category of "but I'm clearly not using it any more" arguments with the borrow checker simply stopped happening. I didn't have to learn anything to benefit. The error messages I used to hit just weren't there.

Lines of code on a monitor

There were smaller things too. ? for error propagation working in more places. impl Trait in argument and return position, which made writing functions that take or return iterators far less of a ceremony. dyn Trait becoming the spelled-out way to say "trait object", which reads as awkward at first and then you're grateful every time you see a bare trait name and know it's a real type, not a dynamic one.

What strikes me, looking back, is that almost none of this was new capability. The language could mostly do these things already. The edition made them learnable. It took the parts I'd been muddling through and gave them shapes I could reason about, and that turned out to matter far more than any single feature. The Rust I write today, where I reach for modules without thinking and rarely argue with the borrow checker about scopes, is downstream of that one edition deciding that ergonomics were worth a breaking-ish change. Worth every bit of the churn.