awm129
awm129

Reputation: 337

How can I reference Self as a parameter type in a trait object?

I'm new to rust and having some difficulty trying to use a Trait as an object with the end goal of storing a collections of structs that implement the Trait.

I'm trying to implement the following but I'm not sure how to resolve the compiler errors. The underlying issue seems to be my use of Self as a parameter of a function defined by a trait:

trait MyTrait {
    fn add(&mut self, data: Self) /*where Self: Sized*/;
}

struct Foo {
    x: i32,
}

impl MyTrait for Foo {
    fn add(&mut self, data: Self) {
        self.x += data.x;
    }
}

struct Bar {
    s: String,
}

impl MyTrait for Bar {
    fn add(&mut self, data: Self) {
        self.s.push_str(&data.s);
    }
}

fn main() {

    let mut foo = Foo { x: 42 };
    let mut bar = Bar { s: "hello".to_string() };

    foo.add(Foo { x: 1 });
    bar.add(Bar { s: " there!".to_string() });
    
    let v: Vec<Box<dyn MyTrait>> = vec![Box::new(foo), Box::new(bar)];
    //v[0].add(Foo { x: 2 });
    //v[1].add(Bar { s: " general kenobi".to_string() });

    assert_eq!(45, foo.x);
    assert_eq!("hello there! general kenobi", bar.s);
}

The compiler gives me errors like:

rror[E0038]: the trait `MyTrait` cannot be made into an object                                                                
  --> src/main.rs:33:12                                                                                                        
   |                                                                                                                           
30 |     let v: Vec<Box<dyn MyTrait>> = vec![Box::new(foo), Box::new(bar)];                                                    
   |            ^^^^^^^^^^^^^^^^^^^^^ `MyTrait` cannot be made into an object                                                  
   |                                                                                                                           
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
  --> src/main.rs:2:29                                                                                                         
   |                                                                                                                           
1  | trait MyTrait {                                                                                                           
   |       ------- this trait cannot be made into an object...                                                                 
2  |     fn add(&mut self, data: Self);                                                                                        
   |                             ^^^^ ...because method `add` references the `Self` type in this parameter                     
   = help: consider moving `add` to another trait

The compiler suggests moving my function to a new Trait... but I'm not sure how that helps me.

I really want to be able to dispatch these from the vector as shown in the commented out code, but that seems to make the situation worse.

I found How to use a trait object that contains a method that returns a reference to Self? but I'm not sure the solution is the same for using Self as a parameter instead of a return type.

What am I doing wrong?

EDIT

It seems despite the trait itself being object-safe, the underlying issue is my add() method cannot be dispatched on a trait object as described in the docs. This is because its possible that Self may not be the same concrete type as self.

My goal is to store a collection of some base type where concrete instances know how to mutate themselves and operate over other instances of themselves. The types of these objects is not necessarily known at compile time. I've implemented such things in many other languages (sometimes with the help of a cast), how would I translate such a design to Rust?

Upvotes: 0

Views: 1180

Answers (2)

FZs
FZs

Reputation: 18619

What you're trying to do, doesn't really make sense. Hear me out!

As far as types are concerned, all instances of a type must behave identically (that's how we know how to handle them). This applies to trait objects too: all instances of the dyn Trait type has to behave identically.

Self is a placeholder: though it's spelled the same, it actually stands for a different type in each impl.

So what you're trying to do is create a type, dyn MyTrait, whose add method takes a value of type... um... sometimes Foo, sometimes Bar? That doesn't work!

That's why the compiler is yelling at you.

This model can not work the way you described. The solution depends on what your actual requirements are:

  1. Disable the add method on the trait objects of your trait

    You can do this by adding a where Self: Sized bound to the add method. But this means that you can only call the add method before casting to a trait object (i.e. before you put them into the Vec).

  2. Make the add function fallible, and let it decide whether it can accept a particular value

    • Use enums

      If you know all types that implement your trait ahead of time, you can swap out your trait for an enum. The enum may contain the different fields of the different types, or, if you also want to use the types separately, you can just wrap them in the enum.

      It would look like this:

      #[derive(Debug)]
      struct DifferentTypesError;
      
      enum MyEnum {
          Foo(Foo),
          Bar(Bar)
      }
      
      struct Foo {
          x: i32,
      }
      
      struct Bar {
          s: String,
      }
      
      impl MyEnum {
          fn add(&mut self, data: Self) -> Result<(), DifferentTypesError> {
              use MyEnum::*;
              match (self, data) {
                  (Foo(self_), Foo(data)) => {
                      self_.x += data.x;
                      Ok(())
                  },
                  (Bar(self_), Bar(data)) => {
                      self_.s.push_str(&data.s);
                      Ok(())
                  },
                  (_, _) => Err(DifferentTypesError)
              }
          }
      }
      
      impl From<Foo> for MyEnum {
          fn from(v: Foo) -> Self {
              Self::Foo(v)
          }
      }
      impl From<Bar> for MyEnum {
          fn from(v: Bar) -> Self {
              Self::Bar(v)
          }
      }
      
      fn main() {
      
          let mut foo = MyEnum::from(Foo { x: 42 });
          let mut bar = MyEnum::from(Bar { s: "hello".to_string() });
      
          foo.add(Foo { x: 1 }.into()).unwrap();
          bar.add(Bar { s: " there".to_string() }.into()).unwrap();
      
          let mut v: Vec<MyEnum> = vec![foo, bar];
      
          v[0].add(Foo { x: 5 }.into()).unwrap();
          v[1].add(Bar { s: " again".to_string() }.into()).unwrap();
      }
      
    • Use dynamic typing with the Any trait

      The Any trait can be used to attempt to downcast to a specific type. Here's how that would work:

      use core::any::Any;
      
      #[derive(Debug)]
      struct DifferentTypesError;
      
      trait MyTrait {
          fn add(&mut self, data: Box<dyn Any>) -> Result<(), DifferentTypesError>;
      }
      
      struct Foo {
          x: i32,
      }
      
      impl MyTrait for Foo {
          fn add(&mut self, data: Box<dyn Any>) -> Result<(), DifferentTypesError> {
              match data.downcast::<Self>() {
                  Ok(data) => {
                      self.x += data.x;
                      Ok(())
                  },
                  Err(_) => Err(DifferentTypesError)
              }
          }
      }
      
      struct Bar {
          s: String,
      }
      
      impl MyTrait for Bar {
          fn add(&mut self, data: Box<dyn Any>) -> Result<(), DifferentTypesError> {
              match data.downcast::<Self>() {
                  Ok(data) => {
                      self.s.push_str(&data.s);
                      Ok(())
                  },
                  Err(_) => Err(DifferentTypesError)
              }
          }
      }
      
      fn main() {
      
          let mut foo = Foo { x: 42 };
          let mut bar = Bar { s: "hello".to_string() };
      
          foo.add(Box::new(Foo { x: 1 }) as Box<dyn Any>).unwrap();
          bar.add(Box::new(Bar { s: " there".to_string() }) as Box<dyn Any>).unwrap();
      
          let mut v: Vec<Box<dyn MyTrait>> = vec![Box::new(foo), Box::new(bar)];
      
          v[0].add(Box::new(Foo { x: 5 }) as Box<dyn Any>).unwrap();
          v[1].add(Box::new(Bar { s: " again".to_string() }) as Box<dyn Any>).unwrap();
      }
      
  3. Do it in a Rustier way

    Since I don't know what exactly are you trying to implement, I can't help with that, but I think it's likely that there's a more idiomatic way to solve your problem.

Upvotes: 4

BallpointBen
BallpointBen

Reputation: 13750

The issue here is that Self makes the trait “object unsafe”. In short, the compiler correctly picks up on the fact that it wouldn't be able to check a call like v[0].add(data) because it can't tell if data: Self, i.e., that data has the same type as v[0], because it doesn't know the type of v[0] anymore (you erased it when you created the trait object).

Moving add to a separate trait would allow you to create a dyn MyTrait, but of course at the expense of MyTrait no longer having an add method.

Upvotes: 0

Related Questions