Hacker News new | ask | show | jobs
by gpm 490 days ago
The borrow checker and lifetimes aren't simply a matter of performance, they are a matter of correctness. Languages without them (go, java, etc) allow for bugs that they prevent - dataraces, ConcurrentModificationException, etc. The fact that you can only write through pointers that guarantee they have unique access is what lets the language statically guarantee the absence of a wide category of bugs, and what makes it so easy to reason about rust code as a human (experienced with rust). You can't have that without the borrow checker, and without that you lose what makes rust different (it would still be a fine language, but not a particularly special one).

You could simplify rust slightly by sacrificing performance. For example you could box everything by default (like java) and get rid of `Box` the type as a concept. You could even make everything a reference counted pointer (but only allow mutation when the compiler can guarantee that the reference count is 1). You could ditch the concept of unsized types. Things like that. Rust doesn't strive to be the simplest language that it could be - instead it prefers performance. None of this is really what people complain about with the language though.

2 comments

I recently came to this realization in a large typescript codebase. It's really important to understand who owns data and who has the right to modify it. Having tools to manage this and make it explicit built into the language is so helpful for code correctness and is especially beneficial for maintaining code you didn't write yourself.
Another great way of handling this if you cannot switch out the language, is to start adopting a more functional approach and also try to keep mutations into one place/less places. So instead of having all the X services/adapter/whatever being able to pass data around that they mutate along the way (like the typical "hiding implementation details in objects/classes"), have all those just do transformation on data and return new data, then have one thing that can mutate things.

Even if you cannot go as extreme as isolating the mutation into just one place, heavily reducing the amount of mutation makes that particular problem a lot easier to handle in larger codebases.

Right this is what I tried to do but unfortunately trying to mark everything immutable in Typescript leads to some very unergonomic type signatures. Hopefully this can improve in the future.
Could you show an example of what you mean? Not sure how not mutating data would lead to more unergonomic type signatures, I'm sure an example would help me understand. Although it wouldn't surprise me TypeScript makes things harder.
You are polluting every variable signature with `readonly`. This also can create cascading effects where making one function accept only readonly variables forces you to declare readonly elsewhere as well. Quite similar, in a way, to Rust.
If I have a complex structure MyStruct that I make recursively readonly it doesn't show up in the IDE as DeepReadOnly<MyStruct> or something like that. It shows up as a huge tree of nested readonly declarations, so the original type is highly obscured.
Couldn't the same guarantees be achieved with immutability? Of course this would be setting aside concerns with performance/resource usage, but the parent is describing an environment where these concerns are not primary.

Personally I find it much easier to grok immutable data, not just understand when concentrating on it, then ownership rules.

Absolutely, with full immutability the borrow checker doesn't give you much at all.

It also doesn't cost you much, the borrow checker just gets out of your way if you just wrap all your immutable data in reference counted pointers (try out imbl [1] for instance). It's not free - there's some syntax overhead compared to a language that was intended to primarily work this way - but it's cheap.

I think it's reasonable to view the borrow checker as a generalization of immutability. Immutability says "no mutation", the borrow checker says "no mutation unless you are the only thing that might be accessing the data". Edit: Worth noting though that the rust standard library and ecosystem is a take on this that doesn't emphasize staying within the fully immutable regime as much as it could, instead preferring to improve performance. A variant of rust that tried to explore keeping more things immutable would be interesting.

Personally my take is that there are some problems which are very naturally represented immutably, and there are others that are very hard to fit in that framework. The borrow checker is general enough to capture almost all of that second category as well. But if you're firmly in the first category, and you aren't worried about every last drop of performance, there's probably some managed language with strong immutability that is a better fit.

[1] https://github.com/jneem/imbl