Programming Language Design 2020-08-06

By Max Woerner Chase

I'm still trying to get a handle on access control in this language. I think I've figured out one hurdle, which is that all types need an access control specification. So, in addition to "a shared vector of shared" and "an exclusive vector of exclusive", there can be "a shared vector of exclusive" and "an exclusive vector of shared". The ones where the access controls match are pretty easy to reason about, as is "exclusive vector of shared". That last just means that the individual entries can't be edited, but they can be added or removed from the vector. Shared vector of exclusive is more troublesome. It sort of makes sense to have as one possible way to implement "tweak every element of the vector, but don't touch the vector itself", but a shared reference can be cloned, which means that if we naively do what I think makes sense, we can have multiple consumers trying to get exclusive locks on the same reference. I'm leaning towards "it shouldn't be possible to get multiple references doing that", since a synchronization-based approach would still be fundamentally unpredictable.

I think this means that I can't have trivial auto-derives for copy/clone, which is fine, and some kind of "derive" macro wouldn't work for container types that are specifically designed to expose more access to their items.

Thinking about this has revealed some kind of asymmetry in my mental model, where I'm assuming that a shared reference to a type with exclusive access to a field casts to a shared reference to that field, while a shared reference to a container with exclusive access to its items results in exclusive access to the items. I think this is useful for the degree of semantic range it permits, but actually implementing this requires some means of opting out of the standard logic. I think this is the kind of thing that unsafe is for in Rust, but I don't know if there's any direct equivalent to what I'm describing, for Rust Vectors.

Anyway, one thing I'm trying to figure out about access controls, is how much non-determinism I want to allow. Like, I could imagine a primitive that's just "release the exclusive lock so some other thread of execution can have it", but if you do a release and a reacquire, what can you expect to have happened during the time it was released? I suppose there could be some signalling layer indicating work to do and work done, but some kind of standard notification system that directly ties into the ownership system seems much more robust, and I think equally expressive. Like, I could imagine just passing the reference into some kind of worker or scheduler, and waiting for it to complete. If there's concurrent work to be done, this does imply some kind of spawning or multi-scheduling system.

I think the things I need to review for this are Notes on structured concurrency, and Notes on a smaller Rust. I'll definitely be considering "structured concurrency", but it's worth emphasizing that my current vision for Toad drops enough expressive power relative to Rust that it cannot be the "smaller Rust" that Saoirse describes. That post is of interest because most of the ideas that apply to "smaller Rust" also apply to Toad. I would describe my thinking for Toad thus: "Many high-level languages have some form of 'box' for their high-level types, that simplifies access control and includes metadata relevant to, for example, garbage collection. I want to experiment with tweaking the capabilities of the 'box'. For situations where Python is 'fast enough', can I design a language with similar expressive power, but that makes it harder to write bugs around concurrent data access?" Realistically speaking, if I'm working on my own, the answer is probably "no", but I'm curious to see what I can accomplish.

Good night.