The usual pitfall of similar !Sync
mut-from-ref tricks is reentrancy, which this doesn't have to worry about. But the fact that the shared references are still "derived from" the mutable reference and can be live across another use of the mutable reference makes me uncomfortable; while I wasn't able to make Miri complain with a single thread, introducing threads could potentially make it possible to cause a UB scenario.
This structure is Send but not Sync, if that helps. The usage goal is something along the lines of:
fn take_and_dispatch(data: &mut SomeData) {
let read_only_data = data.as_read_only();
task_1(data);
task_2(data);
task_3(data);
}
where task_1
, etc. take a &SomeDataReadOnlyRef
.
Why do you (think you) need this? You should try to describe the actual situation instead of generic placeholders like "data", because in general, all of this seems very dubious.
If you have a RefCell
, that means you can't perform compile-time borrow checking over shared references. Trying to circumvent it is unlikely to be possible correctly.
I don't think this is your intention, but this comes across as dismissive and condescending. Please trust that I've explored alternatives and that have come to this approach after evaluating their trade-offs. Additionally, I'm not sure what you're saying here -- get_mut()
on a RefCell
is a comple-time bypass of runtime borrow checking and is safe code as long as you have a mutable reference to that cell, which I do have here.
Is there a reason you can't do something like this?
Yes, I could do that. The issue is that in practice, the true data structure is actually a nested data structure of slotmaps that could contain potentially hundreds of data arrays in RefCell
s. For context, this is for the gecs crate, which generates an archetype ECS data structure at compile-time. Shallow-cloning every component array in the ECS world is unlikely to optimize well and the process of building it would be costly as the base structures grow. My goal is to create a more lightweight shim where I know I'm holding a mut
reference to the data structure, but allow only read-only access to it, and bypass the RefCell
runtime borrow check (because I know I'm holding it mutably anyway). There's also no trait-based solution to this because traits don't let you borrow individual fields, which is a requirement for this data structure.
For additional context, the reason I'm doing this is that slotmap lookups have a cost. They introduce one level of indirection between key and data. My goal here is to introduce an execution context where you can lookup a key once, retrieve its direct index in the dense list, and then continue operating with that index directly without repeatedly taking the cost hit for slot indirection and version checking. This is a fine thing to do so long as the slotmap's dense list doesn't change at all, which you could theoretically guarantee if the slotmap is read-only. In this case, however, because the slotmap dense data is stored in RefCell
s (to allow actual borrowing of data elsewhere), there's no good way to access them in an immutable context. I'd like to create a guarantee that I have mutable access to the data, but that it's also read-only, without having to create a large new data structure holding lots of references to the raw data.
The alternative here, if I want to avoid runtime checks, would be to simply use direct indices anyway and just do so unsafely by manually enforcing the "the ECS world never changes" constraint, but I'd like to provide a safer interface for doing so.