This isn't quite right. The key thing is that Pony uses the actor model, where an "actor" is an object, a green thread, and an MPSC queue, all bundled together into a single conceptual unit. These MPSC queues are the only synchronization primitive; there aren't mutexes (which means that Pony programs can't internally deadlock, though they can livelock). For this reason, for any given reference (pointer) to a piece of data, you can have any two of mutation, aliasing, and concurrency (i.e., sending the reference to another actor's queue, which doesn't count as mutating the actor). But you can't have all three, because that would allow data races.
Consequently, three "reference capabilities" fall out of this design:
- "iso": allows mutation and concurrency, but not aliasing.
- "val": allows aliasing and concurrency, but not mutation.
- "ref": allows mutation and aliasing, but not concurrency.
The other three are more for generic kinds of programming or to facilitate more complicated tricks:
- "box": only allows aliasing, without mutation or concurrency. Subtype of both val and ref.
- "trn": allows mutation and aliasing, but the aliases are box and so don't themselves allow mutation. Also, you can subsequently change it to either ref or val, to get either mutable aliasing or concurrency (but not both).
- "tag": allows aliasing and concurrency, but not mutation, and (unlike any of the others) also doesn't allow reading the data. The only things you can do are pointer comparisons, and sending something to the referent's queue if the referent is an actor (again, this doesn't count as mutating the actor). Subtype of all the other ones; they all allow tag aliases even if they don't otherwise allow aliasing.
Most of these would seem to be allowed under Rust semantics, except that Rust does not conflate references/pointers and MPSC channel ends; there's no notion of "identity" for the sending half of a MPSC channel like there seems to be for a Pony ref. But if you have one of these you can clone it and send it wherever.
The main thing you can't do on the Rust side is mutable aliasing. Most languages allow this, and Pony also allows it with ref and trn, so long as the aliasing is all within a single actor, precluding data races (though not other kinds of aliasing bugs). Rust does not allow this, in part because making it work in a memory-safe way in the presence of algebraic data types requires a runtime garbage collector, which Pony has and Rust doesn't. Some use cases for mutable aliasing are addressed by Rust's interior-mutability types like Cell and RefCell, but these have limitations compared to full unrestricted mutable aliasing.
Other than that, most of what Pony does could, in principle, be written as a library in Rust, though the lack of language-level tracing garbage collection would mean that the ergonomics would be a lot worse and you wouldn't necessarily have the same guarantees around things like deadlock prevention.
I've been working on a multithreaded interpreter language which uses a lockfree algorithm to communicate.
I also have a buggy incomplete implementation of runtime reference passing.
The idea is that you request access to some data and then receive a reference that you can read or write to then you pass the reference to the next requester.
In theory everyone can read at the same time and writers can run sequentially. And alternate between reading and writing. This should prevent data races.
I want to avoid contention and blocking and synchronization cost and complexity. I think the compiler can generate thread safe code with the right design but it is not trivially easy.
Consequently, three "reference capabilities" fall out of this design:
- "iso": allows mutation and concurrency, but not aliasing.
- "val": allows aliasing and concurrency, but not mutation.
- "ref": allows mutation and aliasing, but not concurrency.
The other three are more for generic kinds of programming or to facilitate more complicated tricks:
- "box": only allows aliasing, without mutation or concurrency. Subtype of both val and ref.
- "trn": allows mutation and aliasing, but the aliases are box and so don't themselves allow mutation. Also, you can subsequently change it to either ref or val, to get either mutable aliasing or concurrency (but not both).
- "tag": allows aliasing and concurrency, but not mutation, and (unlike any of the others) also doesn't allow reading the data. The only things you can do are pointer comparisons, and sending something to the referent's queue if the referent is an actor (again, this doesn't count as mutating the actor). Subtype of all the other ones; they all allow tag aliases even if they don't otherwise allow aliasing.