Remix.run Logo
tialaramex 3 days ago

> Seasoned Rust coders don’t spend time fighting the borrow checker

I like the fact that "fighting the borrow checker" is an idea from the period when the borrowck only understood purely lexical lifetimes. So you have to fight to explain why the thing you wrote, which is obviously correct, is in fact correct.

That's already ancient history by the time I learned Rust in 2021. But, this idea that Rust will mean "fighting the borrow checker" took off anyway even though the actual thing it's about was solved.

Now for many people it really is a significant adjustment to learn Rust if your background is exclusively say, Python, or C, or Javascript. For me it came very naturally and most people will not have that experience. But even if you're a C programmer who has never had most of this [gestures expansively] before you likely are not often "fighting the borrow checker". That diagnostic saying you can't make a pointer via a spurious mutable reference? Not the borrow checker. The warning about failing to use the result of a function? Not the borrow checker.

Now, "In Rust I had to read all the diagnostics to make my software compile" does sound less heroic than "battling with the borrow checker" but if that's really the situation maybe we need to come up with a braver way to express this.

Austizzle 3 days ago | parent | next [-]

I think the phrase _emotionally_ resonates with people who write code that would work in other languages, but the compiler rejects.

When I was learning rust (coming from python/java) it certainly felt like a battle because I "knew" the code was logically sound (at least in other languages) but it felt like I had to do all sorts of magic tricks to get it to compile. Since then I've adapted and understand better _why_ the compiler has those rules, but in the beginning it definitely felt like a fight and that the code _should_ work.

milch 2 days ago | parent | prev | next [-]

I've heard lots of people complain during the "exploratory" phase of writing code - maybe you haven't fully figured out how to write the code yet, you're iteratively making changes and restructuring as you go. Most languages make this easy, but with Rust e.g. if you add a lifetime to a reference in a struct it usually becomes a major refactor which can be frustrating to deal with when you're not even sure yet if your approach will work. More experienced devs would probably just use Rc or similar in that case to avoid the lifetime, and then maybe go back and refactor later once the code is "solidified", but for newer devs - they add the &, see the compiler error telling them it's missing a lifetime annotation, spend 30min refactoring, and that's how these stereotypes get reinforced

zorked 2 days ago | parent | prev [-]

Ah, thanks for the historical take. I learned Rust recently. I like it. I never fought the borrow checker. I was sometimes happily protected by the borrow checker. I never understood what people were talking about.

tialaramex 2 days ago | parent [-]

Right, in early Rust, years ago, it's not even legal to do this:

    fn main() {
        let mut x = 5;
        let y = &x;
        let z = &mut x;
    }
The original borrowck goes oh no, y is a reference to x and then z is a mutable reference to x, those can't both exist at the same time, but the scope of y hasn't ended so I give up here's an error message. You needed to adjust your software so that it can see why what you wrote is fine. That's "fighting the borrow checker".

But today's borrowck just goes duh, the reference y goes away right before the variable z is created and everything is cool.

These are called "Non-lexical lifetimes" because the lifetime is no longer strictly tied to a lexical scope - the curly braces in the program - but can have any necessary extent to make things correct.

Further improving the ability of the borrowck to see that what you're doing is fine is an ongoing piece of work for Rust and always will be†, but NLL was the lowest hanging fruit, most of the software I write would need tweaks to account for a strict lexical lifetime and it'd be very annoying when I know I am correct.

† Rice's theorem tells us we can either have a compiler where sometimes illegal borrows are allowed or a compiler where sometimes borrows that should be legal are forbidden (or both, which seems useless), but we cannot have one which is always right, so, Rust chooses the safe option and that means we're always going to be working to make it just a bit better.