Hello rust experts, I am trying to build a rust game engine with a highly ergonomic API. I am currently using the nightly feature arbitrary_self_types, and is wondering how possible would it be for arbitrary_self_types to be compatible with trait objects.
I have a type Manifestly<’m, T> that serves as a custom borrow guard that contains a RefMut and the UUID, which I need to use as the self type, which derefs to type T.
pub struct Manifestly<'m, T: ?Sized> {
pub(crate) inner: (Uuid, *mut Registry),
pub(crate) borrow: std::cell::RefMut<'m, T>,
}
I also have a type Manifested<T> that serves as UUID handle.
pub struct Manifested<T: ?Sized + Manifest + 'static> {
pub(crate) inner: Option<(Uuid, *mut Registry)>,
phantom: PhantomData<T>
}
In my architecture all primary objects live inside RefCells in a hashmap inside the “Registry”, mapped to UUIDs.
pub struct Registry {
manifestations_by_uuid: HashMap<Uuid, (TypeId, RefCell<Box<dyn Anything>>)>,
}
Calling “borrow()” on a Manifested<T> struct borrows the RefMut from the Registry and gives you the Manifestly<’m, T> borrow guard.
In my framework both Manifested<T> and Manifestly<’m, T> supports dyn types as T due to the ?Sized bound, eg: Manifested<dyn Essence>, Manifestly<’m, dyn Essence>.
and it would be ideal for my trait API to look like this
//========== Trait declarations ==========//
// Trait Inspect is for custom egui inspectors
#[manifestable]
trait Inspect {
fn inspect(self: Manifestly<Self>, ctx: &mut InspectContext, ui: &mut egui::Ui);
}
// Trait Essence act as a component of an ECS architecture
#[manifestable]
trait Essence {
fn start(self: Manifestly<Self>, ctx: &mut EssenceContext);
fn update(self: Manifestly<Self>, ctx: &mut EssenceContext);
}
(Note: #[manifestable] and #[manifest_as(Trait)] are proc macros for my inventory-based type registry system - they handle implementing of core traits handle and type registration.)
//========== Implementation following trait API: ==========//
#[manifestable]
struct TransformEssence {}
#[manifest_as(Inspect)]
impl Inspect for TransformEssence {
fn inspect(self: Manifestly<Self>, ctx: &mut InspectContext, ui: &mut egui::Ui) {
}
}
#[manifest_as(Essence)]
impl Essence for TransformEssence {
fn start(self: Manifestly<Self>, ctx: &mut EssenceContext, ui: &mut egui::Ui) {
}
fn update(self: Manifestly<Self>, ctx: &mut EssenceContext, ui: &mut egui::Ui) {
}
}
//========== Usage code ==========//
fn call_polymorphically(essence: Manifested<dyn Essence>) {
let guard = essence.borrow(); // Manifestly<'_, dyn Essence>
guard.update(); // ❌ Error: not dyn compatible because trait method uses arbitrary self type
}
However, Manifestly<Self> as the self parameter makes the trait not dyn compatible.
let essence: Manifested<dyn Essence> = get_some_essence();
let manifestly = essence.borrow(); // Manifestly<'_, dyn Essence>
manifestly.start(&mut context); // ❌ Currently doesn't work with dyn
Declaring “where Self: Sized” makes the method not available on the trait object.
#[manifestable]
trait Essence {
fn start(self: Manifestly<Self>, ctx: &mut EssenceContext) where Self: Sized;
fn update(self: Manifestly<Self>, ctx: &mut EssenceContext) where Self: Sized;
}
// adding “where Self: Sized” to the methods makes those methods not available on the trait object “dyn Essence”.
Why I need “self: Manifestly<Self> to be trait-object compatible”:
-
ability to call a trait method that uses “
self: Manifestly<Self>” calling the method fromManifestly<dyn MyTrait>. I would have a polymorphic collection ofManifested<dyn Essence>, and for each of them, I would want to call borrow() on theManifested<dyn Essence>to obtainManifestly<’m, dyn Essence>and invoke the trait method. -
the ability to temporarily unborrow the RefMut inside a method using it. I have a trait “Encompass” in my game engine which is implemented for every primary object. It recursively iterates the owned children of each object, used for deep cloning purposes. Lets say I have an object A, and object B is the parent of object A. inside the start() method of object A, I might want to deep clone object B whose Encompass will cover object A and need to borrow object A to iterate over object A’s children as part of the whole process, and needs object A in an unborrowed state. But to call start() of object A, object A needs to be borrowed in the form of a RefMut from its RefCell in the registry. Having &self as the self arg in start() means you can’t drop “self” to unborrow the RefMut. But having
RefMut<Self>(or in my caseManifestly<Self>) as the self arg means you can temporarily drop “self” to unborrow it so that the object will be freed for Encompass’s object traversal to borrow it. -
self parameter containing the UUID. For API cleaniness, there would be operations like ctx.record_undo() and ctx.schedule_save() which needs the UUID identity of “self”, which uses the UUID to look up the actual object in the registry to perform those operations.
Impl Inspect for TransformEssence {
fn inspect(self: Manifestly<Self>, ctx: &mut InspectContext, ui: &mut egui::Ui) {
if ui::button(“add one”).clicked() {
self.x += 1;
ctx.schedule_save(self); //UUID contained in Manifestly<Self> self arg allows operation to target the object in the registry
}
}
}
- using enum instead of trait objects would not work because users of the game engine would write their own types that cannot be included in a library-defined enum
So, Is arbitrary_self_types with dyn compatibility theoretically possible given my Manifestly contains RefMut? Would an RFC for this be welcome, or is this fundamentally incompatible with Rust's object model?
I'm happy to provide more details, test implementations, or help with RFC drafting if there's a viable path forward. If this is possible it will enable my game engine to have a very clean and ergonomic API.







