Live data from Hacker News

Group Borrowing: Zero-cost memory safety with fewer restrictions

verdagon.dev

31–40 of 87 posts

Re: Group Borrowing: Zero-cost memory safety with fewer restrictions

#31
I’m pretty impressed, but also skeptical. There’s a contradiction in this post.

Near the beginning of the post is a list of use cases for mutable aliasing. "Back-references", "doubly-linked lists", "delegates" - all cases where you have persistent objects with references to each other, i.e. a cyclic graph of objects.

Near the end, the post says: “The mutual isolation restriction will influence our programs' data to look like trees, similar to how Rust's borrow checker does.” In other words, the proposed approach can’t actually support those use cases!

The post continues by saying it improves on Rust’s borrow checker by having “much more relaxed rules around how we access those trees from the outside”. That may be true, and I’m legitimately looking forward to playing with this in Mojo. But it’s a much smaller improvement (while still coming at the cost of added complexity). It doesn’t really “get the best of both worlds” as the post says earlier.

To be fair, there may be ways to apply similar ideas to get some subset of use cases for cyclic object graphs to work. Some of verdagon’s past blog posts have proposed mechanisms that do something like that. But that would be a whole different thing from what’s being proposed here.

Re: Group Borrowing: Zero-cost memory safety with fewer restrictions

#32
post #31

I’m pretty impressed, but also skeptical. There’s a contradiction in this post. Near the beginning of the post is a list of use cases for mutable aliasing. "Back-references", "doubly-linked lists", "delegates" - all cases where you have persistent objects with references to each other, i.e. a cyclic graph of objects. Near the end, the post says: “The mutual isolation restriction will influence our programs' data to l…

It could be made to work in a language like Haskell, where cyclic structures can arise only in limited circumstances (recursive `let` groups) and can be given a distinct runtime representation.

Re: Group Borrowing: Zero-cost memory safety with fewer restrictions

#33
post #24

In memory-safety discussions it needs to be borne in mind that 'simply fix the hardware' is also a viable approach; or at least it's a lot more viable than it was or seemed to be a few decades ago https://en.wikipedia.org/wiki/Capability_Hardware_Enhanced_R... . Memory-safe BSD and applications running on real memory-safe RISC-V hardware apparently already exist, though obviously, yes, even in a best case existing ar…

True. To be fair, CHERI guarantees only security, while borrow checking (when it works) guarantees both security and correctness. If you write to a freed pointer or out-of-bounds, CHERI guarantees that your write either crashes or lands somewhere harmless, rather than corrupting other data. But borrow checking guarantees you won't perform such writes in the first place.

Yet while correctness may excite programmers, it's security that gets the decision makers' attention. At the end of the day, security is just much more impactful.

We'll see if Morello ever makes its way to the types of CPUs used on phones and higher-performance devices.

Re: Group Borrowing: Zero-cost memory safety with fewer restrictions

#34
post #29

Earlier quoted context omitted.

The general rule to prevent any data races (as I guess) between threads is that at any point of time there can be either one writer or multiple readers to the same object. Rust guarantees this by not allowing to have any other references if you have a read-write (mutable) reference. Note that Rust is more strict than necessary - theoretically you can have multiple writable and readable references, but not use them si…

> And by the way if you know more methods or ideas please share them! Use a transaction manager (HTM or STM). The STM is going to boil down to lockfree synchronization, logging, and retry. Transactions can fail and retry. But ultimately, all inter-thread communication boils down to programs (or libraries) using barriers for acquire/release semantics and/or using compare/swap and atomic read-modify-write. > at any poi…

> Time is a slippery notion in concurrency. Most language-level memory models (e.g. Java) or even hardware-level concurrency models focus on happens-before relations, which are induced by executing certain operations

You are correct, but for simplicity we can assume that the CPU executes one operation at a time and still have a valid model for reasoning about data races.

> But ultimately, all inter-thread communication boils down to programs (or libraries) using barriers for acquire/release semantics and/or using compare/swap and atomic read-modify-write.

Interesting. I read about STM and it reminded me of "modification box" algorithm for modifying immutable trees, which also uses versions. So it is another clever but complicated trick. Also it seems that it requires locking? That's not very good given that locks often need syscalls.

Re: Group Borrowing: Zero-cost memory safety with fewer restrictions

#35
post #31

I’m pretty impressed, but also skeptical. There’s a contradiction in this post. Near the beginning of the post is a list of use cases for mutable aliasing. "Back-references", "doubly-linked lists", "delegates" - all cases where you have persistent objects with references to each other, i.e. a cyclic graph of objects. Near the end, the post says: “The mutual isolation restriction will influence our programs' data to l…

I think you are right. The post talks about mutable vs immutable borrows, and how to have multiple mutable borrows, safely. But doubly-linked list are hard in Rust due to ownership, and not borrowing. (Doubly linked lists could be supported by "splitting" and "merging" ownership: having "half"-owners, and then you can merge ownership.)

I understand the reason for having 1 mutable xor n immutable references... but I think Rust made the wrong choice here: now we see two versions of each method, one with mutable and another without mutable. It's similar to the async coloring problem, but instead of async / not async, in Rust you have mutable and non-mutable. It would simplify things a lot for the programmer (not for the compiler, OK), if all borrows are mutable. Sure the compiler can't always emit noalias, and multi-threading gets slower... But with a sane memory model (as in Java), I think it would be simpler for the programmer.

Re: Group Borrowing: Zero-cost memory safety with fewer restrictions

#36
post #31

I’m pretty impressed, but also skeptical. There’s a contradiction in this post. Near the beginning of the post is a list of use cases for mutable aliasing. "Back-references", "doubly-linked lists", "delegates" - all cases where you have persistent objects with references to each other, i.e. a cyclic graph of objects. Near the end, the post says: “The mutual isolation restriction will influence our programs' data to l…

It could be made to work in a language like Haskell, where cyclic structures can arise only in limited circumstances (recursive `let` groups) and can be given a distinct runtime representation.

Well, if everything is immutable like in Haskell, then you don't really run into the problems described in the blog post in the first place. You can live with Rust's "no mutable aliasing" rule instead.

Re: Group Borrowing: Zero-cost memory safety with fewer restrictions

#37
post #29

Earlier quoted context omitted.

> And by the way if you know more methods or ideas please share them! Use a transaction manager (HTM or STM). The STM is going to boil down to lockfree synchronization, logging, and retry. Transactions can fail and retry. But ultimately, all inter-thread communication boils down to programs (or libraries) using barriers for acquire/release semantics and/or using compare/swap and atomic read-modify-write. > at any poi…

> Time is a slippery notion in concurrency. Most language-level memory models (e.g. Java) or even hardware-level concurrency models focus on happens-before relations, which are induced by executing certain operations You are correct, but for simplicity we can assume that the CPU executes one operation at a time and still have a valid model for reasoning about data races. > But ultimately, all inter-thread communicati…

> Also it seems that it requires locking? That's not very good given that locks often need syscalls.

AFAIU in most STMs all the fast paths are implemented using lock-free algorithms with just barriers.

Re: Group Borrowing: Zero-cost memory safety with fewer restrictions

#38
post #31

I’m pretty impressed, but also skeptical. There’s a contradiction in this post. Near the beginning of the post is a list of use cases for mutable aliasing. "Back-references", "doubly-linked lists", "delegates" - all cases where you have persistent objects with references to each other, i.e. a cyclic graph of objects. Near the end, the post says: “The mutual isolation restriction will influence our programs' data to l…

I think you are right. The post talks about mutable vs immutable borrows, and how to have multiple mutable borrows, safely. But doubly-linked list are hard in Rust due to ownership, and not borrowing. (Doubly linked lists could be supported by "splitting" and "merging" ownership: having "half"-owners, and then you can merge ownership.) I understand the reason for having 1 mutable xor n immutable references... but I t…

But how would you prevent use-after-free? In other words, how do you prevent a child object's method from reaching out to its parent object and, say, clearing the container that contains the child object? Specifically, how do you do that without reference counting or garbage collection.

Re: Group Borrowing: Zero-cost memory safety with fewer restrictions

#39
post #31

I’m pretty impressed, but also skeptical. There’s a contradiction in this post. Near the beginning of the post is a list of use cases for mutable aliasing. "Back-references", "doubly-linked lists", "delegates" - all cases where you have persistent objects with references to each other, i.e. a cyclic graph of objects. Near the end, the post says: “The mutual isolation restriction will influence our programs' data to l…

This approach solves one borrow checking pain point (by allowing temporary mutable aliasing, even across function boundaries), but the post might actually be a bit conservative in saying it will influence our programs' data to look like trees.

As a thought experiment, I've been imagining how this would handle an architecture like e.g. Google Earth's, where a program's classes are divided into multiple "layers". For example, an upper "business logic" layer (DocumentManager, Document, CardManager, Card, StateManager) might have references to the more general "services" lower layer (NetworkService, FileService).

With Nick's system, we could have a group annotation on these various upper-layer classes, like e.g. `Document[s: group IService]`. With these annotations, these upper-layer classes can have references to the services themselves (though not their contents). This might let services' methods mutate the services' contents even though others have references to them (though not their contents). The services' methods would have to communicate that they're modifying the contents of the services (via a `mut` annotation), and the compiler would prevent holding references to the services' contents across those callsites. But also, those contents are private anyway, so the user wouldn't have those references anyway.

If that turns out to be true (Nick and I are still exploring it) then he's found a way to make borrowing work for some DAG-shaped program architectures, rather than just strictly tree-shaped architectures.

On top of that, if we compose this approach with linear types, I think we can get at least halfway towards compile-time-memory-safe back-references. TBD whether that works, but that would be pretty exciting.

TL;DR: My saying it only supports tree-shaped programs is me hedging and being conservative.

Still, until these things are proven to work, I should be more consistent in when I'm conservative vs optimistic. I'll update the article to address that. Thanks for pointing that out and helping me be more consistent!

Re: Group Borrowing: Zero-cost memory safety with fewer restrictions

#40

Earlier quoted context omitted.

Ideally, the rules for single-threaded references and references that are allowed to be shared across threads would be different.

Even with a single thread you need to ensure that the object you are accessing, still exists and was not freed.

If you're talking about stack objects, that's what lifetimes are for.

If you're talking about heap, you can accomplish that by restricting that the references can't be used to free or invalidate the memory. But you could still allow them to be mutable.

Post reply on HN