That's not the issue here- there are plenty of schemes that could be used to enforce the rule, including just copying Rust's if you wanted.
The issue is that a bunch of existing code actively holds and uses multiple mutable references to the same objects. It would simply not be able to adopt the chosen scheme, regardless of how it's spelled.
That's what this "region borrow checking" solves: if a pure function (or block) regards all previously existing memory as one immutable region, it doesn't matter if there was any mutable aliasing happening before, because everything inside that region is shared and immutable now.
Don't get me wrong, it's not the only missing piece for C++; C++ lets pointers escape to other threads which might modify the objects while our thread considers them immutable. Vale solves this by isolating threads' memory from each other (except in the case of mutexes or Seamless Concurrency). Luckily, there are plenty of schemes that can solve that particular problem for C++.
If I had infinite time I would love to figure out how to implement this into C++, after the proof-of-concept in Vale. It's a fascinating topic, and an exciting time in the memory safety field, full of possibilities =)
> > a bunch of existing code actively holds and uses multiple mutable references
> if a pure function (or block) regards all previously existing memory as one immutable region
Yes, but existing code doesn't do that. So you couldn't leverage this against existing code which is intentionally mutating shared memory. Outside of a rewrite it must continue to do so in order to function.
Unless you meant that new functions could be annotated as pure in order to avoid future errors in code not yet written?
I'm not so sure a mandatory system would go over well for C++ since many who use it want shared mutability. I'd be fine with an opt-in system of the sort D seems to be headed towards but I'm not really interested in the tight constraints imposed by Rust. I'm not writing OS kernels or crypto routines over here.
> Unless you meant that new functions could be annotated as pure in order to avoid future errors in code not yet written?
Precisely. New functions can be written to treat all pre-existing memory as immutable, and we can be confident that that data won't change.
For example, if the pure function sees a unique_ptr<MyThing> somewhere in pre-existing memory, we'll know that that MyThing existed at the time of the pure function call, and will keep existing until the end of the pure function call; nobody can destroy it in-between.
Also, it's surprising how many functions in C++ are already effectively pure, and can add the annotation with little (or zero) refactoring. But we don't have to do this, the benefit for new code is enough.
> I'm not so sure a mandatory system would go over well for C++ since many who use it want shared mutability
This is opt-in; we don't have to annotate all our functions as pure, and we can still use shared mutability freely outside of pure functions. We can then hand a shared-mutable blob of data to a pure function, which will then treat it as a shared-immutable blob of data.
That's why I like this approach: one can write an entire program without it, and one can start using it whenever they want. It composes well like that.
Also, if one wants to leverage the region borrow checker outside of pure functions, that's possible too. In Vale, even when not in pure functions, one would make use of `iso` objects (little isolated sub-regions in an otherwise shared-mutable region, similar to Pony's `iso`) and treat those as immutable or mutable as one desires. I suspect it could work in C++ too, but nobody's tried it so I can't say for certain.
no, `const` means you can't modify the data through that `const` variable (excepting shenanigans), not that it's immutable. It can be rather confusing.
> no, `const` means you can't modify the data through that `const` variable (excepting shenanigans), not that it's immutable.
But that's the whole point, isn't it? I mean the selling point of these lifetime annotations is to allow developers to specify that data within a thread cannot be modified by the code running in that thread.
No, const can be added to objects at any declaration or callsite, meaning there can be many const- and non-const references to the same object within the same scope/thread/program/address space/execution context.
I think you are discussing different things, and in the process missing the whole point.
It's one thing for an object to be immutable throughout is life cycle. That's immaterial to this discussion.
It's an entirely different thing that the same object cannot be changed within specific contexts.
If you want to ensure that a thread has read access to an object and it cannot be changed accidentally then passing a const reference to that object already ensures that. That's pretty much the whole point of const.
I think you are missing that const is insufficient for safe concurrency; it requires programmer discipline to ensure there are no shared mutable references.
Const also has no bearing on lifetimes. Constexpr/consteval do, but those objects always have static lifetime so it's sort of irrelevant.
> Vale solves this by isolating threads' memory from each other (except in the case of mutexes or Seamless Concurrency).
Given the whole point of threads is concurrency while sharing a memory space, and given we already have semantics on which data is thread local and how the ownership of heap memory should be handled, what's the point of that?
To me it sounds like some people fail to understand how the complexity they are trying to pile onto C++ is slowly killing C++ due to the sheer volume of all the cognitive load they're trying to add.
No, that is not what this "region borrow checking" solves! Both Rust and your "pure functions" are perfectly capable of enforcing this on new code, that is not the point.
Neither of them can address the line you quoted from the RFC. The existing code they're talking about is not pure. It uses multiple non-const pointers to the same object, at the same time, and temporarily carving out immutable access in between those uses is not the hard part- Rust's scheme handles that just fine already.
>>if a pure function (or block) regards all previously existing memory as one immutable region, it doesn't matter if there was any mutable aliasing happening before, because everything inside that region is shared and immutable now.
Pure functions don't usually need any of this. If they are handed a pointer it has to be to immutable data or not shared by another thread.
> C++ doesn't have a concept of immutable data, or data not shared by another thread, which is part of the challenge.
Does it really need it, though? In the real world C++ apps already use higher-level constructs to handle this sort of semantics. For instance, Qt offers it's moveToThread member function to specify thread affinity.
The issue is that a bunch of existing code actively holds and uses multiple mutable references to the same objects. It would simply not be able to adopt the chosen scheme, regardless of how it's spelled.