Comment by verdagon
3 hours ago
Sorry, I think my attempts to simplify/explain ended up confusing things. I'll try to be a little more precise.
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 think I'm still failing to adequately explain what I mean. Let me try being absurdly explicit. The following is ordinary Rust code, and the reader is supposed to pretend that the two functions prefixed with valen_ are in Valen instead.
This compiles except for the *val = 17 line. (rustc's error message is a bit confused, and maybe I'll file a bug about that. If one follow's rustc's advice, the weird error turns into a less weird error.)
My point is that Receiver::set() requires a promise that its parameter's referent is immutable for the entire lifetime 'a, which exceeds the lexical duration of set() itself. And valen_outer_fun in Rust knows this to be true as a result of Rust's shared-xor-mutable system, and Rust's borrow checker verifies this:
a) the code would compile if I removed the offending *val = 17
b) the code, correctly, does not compile as written
c) If I force the issue by replacing *val = 17 with:
then it panics and we can all imagine we're using C++ instead of Rust :)
But valen_inner_fun does not mutate val, and if I understand right then this means that Valen would accept its Valen equivalent without mut(val), and this seems unsound to me.
When I first thought of this, I imagined set() instead being a work-dispatching function that would run its passed-in closure and hit UB due to a data race with the *val = 17 line, and my edit was my realizing that there's a simpler non-concurrent example.