verdagon
Joined 8 years ago
Joined 8 years ago
I think there's a way to make the invalidation annotation (like Valen's `mut`) work nicely and feel ergonomic. Carbon is also facing this particular challenge, they have a literal invalidate annotation too.
I started with a sort of "compile-time generational references" approach, where every reference would also remember when it was made, at compile-time. Then that evolved into a couple more things until I finally figured out this reverse approach. We'll see if it sticks.
Hope you all enjoy!
In Rust, a reference is forever shared/immutable or forever unique/mutable.
In Valen, a reference is... "it depends". Specifically, it depends on the context.
It's similar to a &GhostCell<T>, where its mutability isn't determined yet because the GhostToken isn't present yet.
So, what determines the mutability at any given point in time? The function's `mut` effects (or lack of them) for the group/path that the reference is pointing to.
So we can imagine a `execute_on_4_threads` function like this:
func execute_on_4_threads<C', F: Func<void, (), C>>(
workgroup: &W,
closure: F
) { ... }
(`Func<void, (), C>` is a trait for a function that returns void, takes no extra parameters, and names its captures as group C).The most relevant fact here is that this function doesn't declare any `mut` effects at all (not on C, not on W's group, nothing), so nothing is being mutated. (And because of that, this function's callees also can't have any `mut` effects; they also can't mutate the data)
Now let's say we changed `workgroup`'s type to `&W in w`, and added a `mut(w)` to the function, to describe that we might modify the workgroup.
At that point, we would still know that we can invoke the closure from 4 threads, because we declared no relationship between `w` and `C`, so the compiler assumes (and enforces) that they're disjoint, have no overlap, nothing in one aliases anything in the other.
Hopefully that helps. Maybe I should write a blog post on this, my posts are usually clearer than my HN comments.
(Also, I'm not sure I understand your edit, if you could clarify that would be much appreciated)
I would say Valen's real benefit here is in making a more ergonomic way for functions to work with an arbitrary number of GhostTokens / brands, and to track the relationships between them. (But I admit, I'm no expert with GhostCell, happy to be corrected by someone here)
If it helps: AFAICT, Valen's borrow checker preserves the same ecosystem-wide concurrency benefits that Rust has. For precedent, check out GhostCell [0] which is not only _compatible_ with Rust's concurrency but gives it some interesting new abilities. Valen's approach could be thought of as a more ergonomic form of GhostCell that better tracks the relationships between multiple groups (brands).
I could give a better answer if we had an example to toss around where we think Valen might force things to be single-threaded.
Edit: it turns out, Ante has path unions! https://www.reddit.com/r/ProgrammingLanguages/comments/1x37b...
That wouldn't be shareable with another thread, because part of it is `mut`. _Theoretically_ we could make this shareable with other threads if we had another kind of effect, let me know if you're curious about that.
The `entity in world.entities[] mut` gives `step` blanket permission to mutate any element inside world.entities. We wouldn't be able to say "all other entities are readonly".
Though, it's worth mentioning that `advance` and `resolve` both receive the entity as a unique reference (since it's the only reference into a group which has a mut effect).
* When a function has a mutable effect into a group, and the function doesn't declare any other groups to alias it (with `in`), it is effectively a unique reference.
* When a function has references into a group, and it declares no `mut` effects into any of them, they are effectively shared/immutable references.
This helps with Valen's structured concurrency in particular, but also helps guard against the single-threaded race conditions in the same way that Rust's borrow checker does. It also helps with Rust interop!
So, TL;DR: Valen lets you choose between shared-xor-mutable and mutable aliasing.
Also note how mutable aliasing is opt-in (via `in`), so there's a subtle influence pushing people towards shared-xor-mutable, so that they only reach for mutable aliasing when it will benefit them.
Short answer: Valen would have something similar to Fn and FnMut (but phrased in terms of effects rather than Fn vs FnMut). In other words, we would be able to express "a closure that does not modify anything it captures", or rather, "a closure that has no mut effects".
That closure, because it doesn't modify anything it captures, would be safe to share among multiple threads in a structured-concurrency-like / std::thread::scope-ish way.
The key here is that one _can_ express immutable references in Valen; an immutable reference is a reference that the containing function doesn't express a `mut` effect for. And once we have immutable references, we get all of the nice concurrency benefits that Rust trailblazed.
I'd also like to make a way to do the above without a function call, perhaps using something like the `parallel` keyword I described in [0].
A no-access reference is an interesting idea. That could be a more powerful way to express may_dangle. In Valen, I hope to have an "opaque" group to express something like that.
I don't know whether it's a good idea, but in Valen I'm trying to decouple access capabilities away from the reference types as much as possible. We'll see if that bet pays off.
[0] https://verdagon.dev/blog/seamless-fearless-structured-concu...
I hope they one day upgrade to full group borrowing, it would be a nice fit for them.