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

the 2018 edition quietly rewired my rust habits

A year on from the 2018 edition, the small ergonomic changes that actually changed how I structure modules and write functions.

A terminal showing Rust code

The 2018 edition landed last December, and I assumed it would be one of those things I'd flip on in Cargo.toml and then never think about again. That turned out to be wrong. Not because of anything dramatic, but because a handful of small ergonomic changes have, a year later, genuinely changed how I write things day to day.

The big one for me is the module system. I never had a clean mental model of the old mod.rs plus extern crate plus path-prefix dance. I could make it work, but I couldn't explain it to someone else without hedging. The new path clarity, where crate::, self:: and super:: mean what they look like they mean, removed a whole category of "why won't this resolve" that I used to just brute-force with use statements until the compiler stopped complaining.

A diagram of module paths

The other change that stuck is ? working in more places and the broader move away from try!. I'd already trained myself onto ? for Result, but having error handling read as a straight line down the function rather than a pyramid of matches is the kind of thing you stop noticing precisely because it's working. Good ergonomics are invisible. You only feel them when you go back to older code and wince.

non-lexical lifetimes did the most work

If I'm honest, the feature I argued with least is the one I understand least: non-lexical lifetimes. The borrow checker simply stopped rejecting code that was obviously fine. The classic case:

fn main() {
    let mut v = vec![1, 2, 3];
    let first = &v[0];
    println!("{}", first);
    v.push(4); // pre-NLL: borrow checker complains. now: fine.
}

Before NLL the borrow of first was considered live until the end of its lexical scope, so that push was an error even though nothing reads first after the println!. I used to "fix" this by adding extra braces to scope the borrow tighter. I don't do that any more, and I had to consciously unlearn the habit, which is its own small tell about how much the old behaviour had shaped my code.

What I didn't expect is how much these changes affect the way I structure new code rather than just how I fix old code. I reach for smaller functions now because passing references around is less of a fight. I split modules earlier because the module system isn't punishing me for it. None of this is in the changelog as a headline, but it's the actual effect.

The migration tooling deserves a mention too. cargo fix --edition did most of the mechanical work on the projects I moved over, and where it couldn't, it at least pointed me at the right lines. I went in expecting a weekend of swearing and got an afternoon of mild tidying.

A year in, the verdict is dull in the best way. The 2018 edition isn't a new language. It's the same language with several papercuts removed, and the cumulative effect of removing papercuts is that you stop bleeding without ever noticing the exact moment it stopped.