Description
Motivation
The essential rule for aliasing in rust is that you can have either multiple shared &
references to a location, or a unique &mut
reference.
This is sometimes too restrictive, e.g. when you have two systems that both share a reference to a World
but you know that they don't access the same data. As an escape hatch rust provides the UnsafeCell<T>
type (and safe wrappers around it), which is special in that it lets you get a &mut T
from a &UnsafeCell<T>
, provided that you manually uphold the safety requirements. Notable, this doesn't change anything about the fact that you can't have two aliasing &mut
s. The only thing that this allows, and that we make use of, is building abstractions like these:
struct World {
storages: UnsafeCell<ActualStorage>
...
}
where you can pass a &World
around and expose some unsafe fn World::get_unchecked_mut<T>(&World, Entity) -> Option<&mut T>
.
Why is this not enough?
This works if you carefully document the unchecked_mut
safety contracts and take care to not violate them. It is still error prone, because when you have a &World
that only lets you access resource Foo
, safe code can do bad things.
This is because all the methods on World
like World::get_resource
are safe, which is usually corrent when treating &World
as an immutable world reference. This isn't the case anymore if it is used as a access restricted kinda-mutable world.
Solution
Add a new wrapper type around &World
(here InteriorMutableWorld
, name up for discussion). Using this type documents intent better and makes writing unsafe code easier than using a &World
directly.
Proposed API
struct World { .. }
impl World {
fn as_interior_mutable(&'w self) -> InteriorMutableWorld<'w>;
}
#[derive(Clone, Copy)]
struct InteriorMutableWorld<'w>(&'w World);
impl<'w> InteriorMutableWorld {
unsafe fn world(&self) -> &World;
fn get_entity(&self) -> InteriorMutableEntityRef<'w>;
unsafe fn get_resource<T>(&self) -> Option<&'w T>;
unsafe fn get_resource_by_id(&self, ComponentId) -> Option<&'w T>;
unsafe fn get_resource_mut<T>(&self) -> Option<Mut<'w, T>>;
unsafe fn get_resource_mut_by_id<T>(&self) -> Option<MutUntyped<'w>>;
// not included: remove, remove_resource, despawn, anything that might change archetypes
}
struct InteriorMutableEntityRef<'w> { .. }
impl InteriorMutableEntityRef<'w> {
unsafe fn get<T>(&self, Entity) -> Option<&'w T>;
unsafe fn get_by_id(&self, Entity, ComponentId) -> Option<Ptr<'w>>;
unsafe fn get_mut<T>(&self, Entity) -> Option<Mut<'w, T>>;
unsafe fn get_mut_by_id(&self, Entity, ComponentId) -> Option<MutUntyped<'w>>;
unsafe fn get_change_ticks<T>(&self, Entity) -> Option<Mut<'w, T>>;
unsafe fn get_mut_by_id(&self, Entity, ComponentId) -> Option<MutUntyped<'w>>;
}
After this abstraction has been built, we should update our internal use of &World
to InteriorMutableWorld
, for example
trait System {
- unsafe fn run_unsafe(&mut self, input: Self::In, world: &World) -> Self::Out;
+ unsafe fn run_unsafe(&mut self, input: Self::In, world: &InteriorMutableWorld) -> Self::Out;
...
}
Prior Art
#5588 builds something similar, but custom built for the QueryState
use case.
Implementation Questions
When we have World::get_resource_mut
and InteriorMutableWorld::get_resource_mut
we don't want code duplication.
Do we
- define
get_resource_mut_inner(&self)
onWorld
and use it in both or - just define the method in
InteriorMutableWorld
and callself.as_interior_mutable().get_resource_mut(self)
inWorld
Same question for get_resource
, but here we could also define it in World
and let InteriorMutableWorld
just call self.0.get_resource
.