CoercePointee for types that hold metadata with something else aside a ptr

I noticed that CoercePointee only works for pointers, but I have been thinking that it could be beneficial if it allowed other types aside pointers.

I believe that this example would be a good use case for it:


// The attribute syntax is only a sketch. What matters is that the key carries the vtable.

#[derive(CoercePointee)]
struct CastKey<T: ?Sized> {
   key: Key,
   #[metadata]
   metadata: <T as Pointee>::Metadata,
}

trait Entity {
    fn on_hit(self: CastKey<Self>, world: &mut World, damage: u32);
}

struct Barrel {
    health: u32,
    position: Vec2,
}

impl Entity for Barrel {
    fn on_hit(self: CastKey<Self>, world: &mut World, damage: u32) {
        let barrel : &mut Barrel = &mut world[self];
        barrel.health = barrel.health.saturating_sub(damage);
        if barrel.health == 0 {
            let center = barrel.position;

            world.remove(self);
            // Explosion!
            world.hit_all_near(center, 5.0, 50);
           
        }
    }
}

while let Some((target, damage)) = world.hits.pop() {
    target.on_hit(&mut world, damage);
}

Calling a trait method on an entity stored in an arena means borrowing it from the world as a &mut dyn Entity, because that reference holds the vtable. However, that borrow basically locks the whole world, so the method can't also take &mut World.

If the key holds the vtable instead, you can call the method on the key and nothing is borrowed from the world. The method gets the key and &mut World as separate arguments, so the borrow checker has nothing to complain about.

What do you think?

1 Like

It could work if Key were pointer-sized.[1] There was an experiment along those lines, but it didn't result in an RFC.


  1. When you dyn coerce a type behind a pointer, the result is always the same size and layout. As a result the ABI for passing a Ptr<dyn Entity> is the same no matter the erased type. If the vtable is inline and the non-metadata isn't pointer sized... or at least, all the same size for a different erased primitive... that would no longer be the case. ↩︎