How to access a supertrait in a trait?

Hello. I have a problem. I'm writing a physics engine, which is where the problem happened. I am currently working on object collision and I have 2 traits: ObjectInterface and MoveInterface: ObjectInterface. In the MoveInterface: ObjectInterface trait, I have the function fn sat(&self, object: &dyn ObjectInterface) -> Option<(f32, Vec2D, Vec2D)>. And here the actual problem is that the compiler complains that I use ObjectInterface in the function. My idea is that a moving object can collide with both a moving object and a standing object. How can this be fixed?

Error:

error[E0658]: trait upcasting coercion is experimental
  --> src/physics_engine/map.rs:34:65
   |
34 |                     println!("{:?}", (*self.dyn_objects[i]).sat(&(*self.dyn_objects[j])));
   |                                                                 ^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: see issue #65991 <https://github.com/rust-lang/rust/issues/65991> for more information

Function:

fn sat(&self, object: &dyn ObjectInterface) -> Option<(f32, Vec2D, Vec2D)>;

As the error hints at, Rust doesn't yet have stable support for automatically converting &dyn MoveInterface to &dyn ObjectInterface.

What you can do is use dyn MoveInterface as a type that implements the ObjectInterface trait. That is, if you can change your function signature to

fn sat<O: ObjectInterface + ?Sized>(&self, object: &O) -> Option<(f32, Vec2D, Vec2D)>;

then it will accept an &dyn MoveInterface. However, that only works if sat() can be generic, which it can't if it going to be used on a trait object, so this basic technique doesn't apply to your situation.

Another possibility is to do the explicit upcasting trick that is often used with dyn Any:

trait MoveInterface: ObjectInterface {
    fn as_object(&self) -> &dyn ObjectInterface;
    // ...
}

// then in each of your implementations...
impl MoveInterface for SomeType {
    fn as_object(&self) -> &dyn ObjectInterface { self }
    // ...
}

// then use it like this:
println!("{:?}", (*self.dyn_objects[i]).sat(self.dyn_objects[j].as_object()));

This is essentially an explicit version of the trait upcasting the compiler doesn't currently support: by implementing the as_object() method, we insert into the MoveInterface vtable a function that can return an ObjectInterface vtable pointer.

The catch is that you have to write fn as_object(&self) in every impl. (It cannot be a default method.)

4 Likes

The fix to this problem (at least for all Sized types) is to introduce another trait for the casting, with a generic implementation.

trait ObjectInterface: AsObject {
    fn some_object_method(&self);
}

trait AsObject {
    fn as_object(&self) -> &dyn ObjectInterface;
}

impl<T: ObjectInterface> AsObject for T {
    fn as_object(&self) -> &dyn ObjectInterface {
        self
    }
}

trait MoveInterface: ObjectInterface {
    fn some_move_method(&self);
}

struct ExampleType;

// no need to implement `as_object` manually anymore :-)

impl ObjectInterface for ExampleType {
    fn some_object_method(&self) {
        println!("called some_object_method for ExampleType");
    }
}

impl MoveInterface for ExampleType {
    fn some_move_method(&self) {
        println!("called some_move_method for ExampleType");
    }
}

fn main() {
    let x = ExampleType;
    let y: &dyn MoveInterface = &x;
    
    let z: &dyn ObjectInterface = y.as_object();
    
    z.some_object_method();
}

(playground)

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 1.05s
     Running `target/debug/playground`

Standard Output

called some_object_method for ExampleType
4 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.