36 ms·
Forcing you into a model up-front can be good, but only if the model is a good one for the situation. It must have as many constraints as the situation calls fo
by verdagon 4y ago
Forcing you into a model up-front can be good, but only if the model is a good one for the situation. It must have as many constraints as the situation calls for, and no more.
Unfortunately, the borrow checker forces two extra constraints which often have little to do with the situation:
- No shared mutability. This is an unrealistic constraint; our world calls for shared mutability all the time. Any time a Go reference becomes an index in Rust, you're experiencing this mismatch.
- Single ownership (in the C++ and Rust sense). Most things can be phrased in terms of single ownership, but not all things should be, and it has a cost.
Rust offers you tools to enforce those constraints, but those constraints are often artificial complexity and shouldn't have been added to a situation to begin with. Same with async/await. They solve a self-imposed problem that e.g. goroutines show us don't need to exist in the first place.
We like these mechanisms because they make the program faster, but most of a program doesn't need to be fast. Only the hot path needs to be optimized, the rest of the program should be simpler, more flexible, more decoupled, and have less constraints.
(This is one of the reasons some domains should use Rc and RefCell more and our community should stop vilifying them: they help provide flexibility to balance the borrow checker's constraints.)
That said, this problem really only arises when one tries to use Rust in a domain it doesn't fit well. There are some domains which do fit the borrow checker's constraints really well, and they become benefits rather than sources of artificial complexity.
- josephcsible 4y ago> No shared mutability. This is an unrealistic constraint; our world calls for shared mutability all the time. Any time a Go reference becomes an index in Rust, you're experiencing this mismatch. Isn't the ease with which a reference can be replaced with an index evidence that you usually don't actually need shared mutability?
- verdagon 4y agoIt can often be quite a pain to use an index. From further up: > For example, in Rust, we often have to use an index or an ID where a regular reference would do in Go. We need to do extra refactoring of function signatures to pass collections down the call stack so we can then use that index/ID... and then find we can't modify a function signature because it's a trait override, and resort to tricky workarounds. In Go, we just use a reference. That choice is decoupled from memory concerns. The problem is that you can't just dereference an index, you instead need all your callers' callers' signatures to take in the collection, causing a minor refactor shock wave in some cases. If that runs into an unchangeable signature (like a trait override, or a public API), it's pretty much game over and you're back to the drawing board. If you're making CLI program or small server, this might not hurt that much. In larger programs, the extra refactoring from this constraint can be quite costly and disruptive.
- MrJohz 4y agoI don't think I understand that example. If I understand correctly, the issue is the question of exclusive mutability, which is why you can't pass the mutable reference itself around. But for some reason you can pass around a mutable reference to the collection and an index? That seems surprising: theoretically, if you have a mutable reference to a collection, you can always transform it into a mutable reference to some subset of its children. Or is this a lifetimes issue, where the lifetime of the individual reference might be longer than the lifetime of the collection as a whole? In that case, it might be that the lifetime annotations aren't correct, or that the code is wrong in the first place. I get the idea that you can't change public APIs and function signatures, but that's true in pretty much every language.
- divs1210 4y ago> No shared mutability. This is an unrealistic constraint; Hard disagree. It is impossible to safely use shared mutable state b/w parallel processes. See how DB isolation levels work - serializable is the only safe way to go if your processes do anything conditional on the current state. To parallelize, you have to partition the DB which is kind of a cheap way to split one physical DB into separate logical DBs. So even DBs - the largest shared mutable states we have - are also not really shared mutable states. They need to be broken down into unshared mutable states to be useful. Shared mutable state is a smell both at the code level and system level.
- verdagon 4y agoJust because shared mutability is sometimes inconvenient for parallelism doesn't mean it's a good idea to outlaw it everywhere. That's like saying "inheritance makes some things easier, so let's use it everywhere we possibly can". Also, you can still apply mutability restrictions at the region/thread/process level. You don't need to apply it to every single object, which would lead to the drawbacks discussed above. If you want to learn more about it, take a look at what Pony is doing.
- pkd 4y agoI think if this comment accurately describes your philosophy then Rust may not be the language for you. This is not a criticism, just an observation. Bryan Cantrill talks about this in "software as reflection of values" [1]. Safety without a runtime is Rust's primary super power, and it fully commits to it by not allowing for affordances that compromise safety as much as possible. If this is simply accidental complexity for you then I am not sure Rust will ever evolve to satisfy you? If you/your team thinks Go makes better tradeoffs there then it's perfectly fine to use it instead - I would say it's even the right choice as IMO, these things matter as much for social dynamics as they do for technical reasons. [1]: https://corecursive.com/024-software-as-a-reflection-of-values-with-bryan-cantrill/ https://corecursive.com/024-software-as-a-reflection-of-valu...