3 ms·
These quotes confused me: > The Rust language model is based around the so-called borrow checker, which tracks the lifetime of all the objects; thus, it can de
by duped 4y ago
These quotes confused me:
> The Rust language model is based around the so-called borrow checker, which tracks the lifetime of all the objects; thus, it can detect safety errors at compile-time and does not require the use of a garbage collector
> Val solves this problem in an entirely different way: it adds restrictions to references, and ensures that nobody can read an object while somebody else is allowed to change it.
Because this is exactly what Rust does (forbid multiple mutable aliasing). After reading the linked reference (1) the difference isn't the program structure but rather that Rust allows references in values whereas Val does not, which obviates the need for ownership semantics and borrow checking (since there are no borrows!).
So my understanding is that both Rust and Val are providing the same guarantee to the program (exactly one writer or any number of readers to values) but choose to do it in very different ways. The interesting thing from the PL design side is that both implementations leak into the semantics of the language (Rust through explicit lifetime annotation, Val through implicit references). This is exactly the thing that other newer systems language designers seem to gloss over or not understand - you can't slap static analysis onto a compiler (takes like: "we can add optional borrow checking in the future") and get the same safety guarantees. That will only go so far, since more expressive semantics make the analysis impossible.
(1) https://www.jot.fm/issues/issue_2022_02/article2.pdf https://www.jot.fm/issues/issue_2022_02/article2.pdf
- charcircuit 4y agoRust didn't originally have borrow checking. It was bolted on later.
- estebank 4y agoThis is technically true, but somewhat misleading. The borrow checker was added pre1.0, and originally wasn't known to be possible to rely on it exclusively for memory management. It was bolted on later, but it changed the language so much that if Rust had been 1.x, after the change we'd be calling the current iteration of Rust "2.0". As such, any production ready language that attempts the same gambit will have to be ready for a painful split that would make the Python 3 migration look like a walk in the park.
- fbdab103 4y agoThe problem with the Python transition is that the language is so flexible that it becomes challenging to understand if/where/when any code will be impacted by a change. Unit testing becomes the static type checker (less so now that Python typing is spreading). For a statically compiled, AOT language, it should be easier to migrate the bulk of the code and identify the hairy bits. Which is not to say it would be easy or fast, but I think Python, Perl or other flexible languages are worst case examples for breaking language change migrations.
- pcwalton 4y agoThis is technically true, but it was "bolted on" in an extremely non-backwards-compatible way†. Tons of APIs had to change throughout the ecosystem, which was thankfully very small at that point. This points to what I and others mean when I say that you can't just "bolt on" Rust's borrow checking semantics to a language that doesn't have one. You technically could add the borrow checker to C++ or D or Zig or whatever, but it would break so much code that in practice the result would be a new language. Also, a relatively minor point, but still worth mentioning: Prior to the introduction of the borrow checker, Rust didn't have any way to share memory across multiple threads, which made the migration easier as there was no need to update multithreaded code to be compatible with the borrow checker. But most languages do have shared memory, which would make adding a borrow checker to them that much harder. † Technically it was a gradual process, starting with the introduction and slow uptake of unique pointers, then the deprecation of @ and migration to Rc/RefCell, and finally a long period of plugging soundness holes, many of those fixes causing small amounts of breakage as I recall.
- teleforce 4y agoD is now testing for borrow checker called ownership borrowing (OB): https://dlang.org/blog/2019/07/15/ownership-and-borrowing-in-d/ https://dlang.org/blog/2019/07/15/ownership-and-borrowing-in... https://users.rust-lang.org/t/dlang-adds-a-borrowchecker-called-the-ob-system-for-ownership-borrowing/42872 https://users.rust-lang.org/t/dlang-adds-a-borrowchecker-cal...
- pizza234 4y ago> So my understanding is that both Rust and Val are providing the same guarantee to the program (exactly one writer or any number of readers to values) but choose to do it in very different ways. The interesting thing from the PL design side is that both implementations leak into the semantics of the language (Rust through explicit lifetime annotation, Val through implicit references) Interesting. Can't Val references (based on my reading of https://verdagon.dev/blog/generational-references https://verdagon.dev/blog/generational-references) be implemented in Rust as smart pointers? It seems that the requirement from the language is ownership (to detect when the reference needs to be freed), besides the error handling.
- verdagon 4y agoA solid question, though that post you linked is about Vale, not Val ;) One day I hope to get a drink with the Val, Vala, and VALE folks and have a hearty chuckle at how this happened. Long story short, generational references like in Vale can't be implemented as efficiently in Rust. The best attempt I've seen so far involves having a side table to store the generations in, which (by my measurements when I tried that exact same approach) is about 4x slower. Vale makes some pragmatic tradeoffs to be able to store generations inline in the stack, containing struct, or containing array, tradeoffs which I believe are antithetical to Rust's direction.
- duped 4y agoThis is very interesting work. I'd be interested to see a comparison between generational references, compacting GCs (which are quite similar in implementation details), and generational GCs that use more traditional scanning approaches. Particularly when it comes to memory pressure and what the generational references + regions winds up looking like.