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

how the 2018 edition quietly retrained my hands

A look back at the Rust 2018 edition and the handful of changes that, four years on, are so baked into how I write code that I forget they were ever new.

A screen full of Rust source code

I went back to fix a bug in some of my own Rust from late 2017 last week, and the thing that struck me wasn't the bug. It was how foreign the code looked. extern crate lines at the top of every file. use paths that started with a bare crate name and made me squint. A ? where I half expected try!. It read like a dialect I used to speak and have since lost the accent for, and the reason is the 2018 edition.

The headline feature was async/await, but that landed later and isn't what changed my day-to-day. The quiet stuff did. The module path changes alone rewired a habit I didn't know I had. Before 2018 I spent real mental energy deciding whether a path was relative to the crate root or the current module, and getting it wrong constantly. The uniform path rules and the crate:: prefix turned that from a guessing game into something I just type without thinking.

// before, roughly
extern crate serde;
use serde::Serialize;

mod config {
    use ::serde::Serialize; // the leading :: dance
}
// after
use serde::Serialize;

mod config {
    use crate::config::Defaults;
    use serde::Serialize;
}

That extern crate line disappearing is a small thing on paper and a large thing in practice. It was pure ceremony, a line you copied to the top of a file to repeat what Cargo already knew from Cargo.toml. Now the dependency edges live in one place. I didn't realise how much that line had been nagging at me until it was gone and the nagging stopped.

A close-up of code on a monitor

The other one that retrained my hands was non-lexical lifetimes. Pre-NLL, the borrow checker held a borrow alive until the end of its enclosing scope whether you'd finished with it or not, so you'd get fought over code that was obviously fine. I used to write little inner blocks { ... } purely to convince the compiler a borrow had ended. I wrote those blocks for years. With NLL the borrow ends where the last use ends, and those defensive braces just evaporated from my style. I genuinely don't write them any more, and I can't tell you the exact day I stopped.

There's a lesson in there about editions as a mechanism, separate from any single feature. Rust got to make async and await into keywords, change how modules resolve, and tighten the borrow checker, all without breaking the crate I wrote in 2015. You opt in per crate with one line in Cargo.toml, and crates on different editions link together fine. That is a genuinely clever bit of language governance, and it's the reason I trust the next edition won't strand me.

So when people ask whether 2018 was a big release, I never quite know how to answer. There was no single feature I'd point at and call life-changing. It was a hundred small frictions removed at once, and the proof is that I can't read my own pre-2018 code without wincing. The edition didn't teach me new tricks so much as let me forget some bad habits, and forgetting, it turns out, is the harder thing to engineer.