What am I missing with trait objects? "cannot be made into an object"

Hi all,

I'm sure the experienced people in this group will find my question trivial and easy to answer. Still, despite a lot of reading, I'm not able to grasp what am I doing wrong regarding trait objects.

Here my problem:

I'm working on an message-passing system (event-based if you want). I've tried to define a base trait for the definition of the messages, so that I can use the trait objects later in the definition of structs methods etc. Here the code snippet:

pub trait IEvent: ToJsonMessage + FromJsonMessage{
    // The get_message_type_name function is used to get the message type name.
    // It returns a string slice.

    fn get_message_type_name(&self) -> &'static str;
}

/// The ToJsonMessage trait is used to serialize a struct into a json string.
pub trait ToJsonMessage{
    // The to_json function is used to serialize a struct into a json string.
    // It returns a string.
    
    // # Arguments
    // * `self` - A reference to the struct.
    fn to_json(&self) -> String
    where Self: Serialize {
        serde_json::to_string(&self).unwrap()
    }
}

/// The FromJsonMessage trait is used to deserialize a json string into a struct.
pub trait FromJsonMessage{
    // The from_json function is used to deserialize a json string into a struct.
    // It takes a json string as input and returns a struct.
    
    // # Arguments
    // * `json` - A string slice.
    fn from_json<T>(json: &str) -> T
    where T: for<'a> Deserialize<'a>{
        let object: Result<T, serde_json::Error> = serde_json::from_str(json);
        object.unwrap()
    }
}

After receiving a message via a messaging-infrastructure, I have to deliver the message to all the Actors (i'm trying to adopt an actor model) that subscribed to the message itself. An Actor can subscribe to more than one message and here is the source of the problem: I need to find a way of, when receiving the message in the Actor (which is a struct) call the correct struct method to handle the message. This method receives as parameter the deserialized version of the json message (in the shape of an implementation of 'IEvent'). so I need a mean to, once I receive the message via a tokio::channel, dispatch it to the correct struct method. I want to avoid having extense if-else statements so I tried the following (and obviously failed):

// The WorkOrderManager struct represents a manager for work orders.
pub struct WorkOrderManager{
    ... // Other irrelevant data here
    list_of_subscriptions: HashMap<String, fn(Arc<dyn IEvent>)> // Here I get the compilation error
}

impl WorkOrderManager {
    // The new function is used to create a new WorkOrderManager.
    // It returns a WorkOrderManager.
    pub fn new() -> WorkOrderManager {
        let (tx, rx) = tokio::sync::mpsc::channel(100);
        
        WorkOrderManager {
           ...// Other irrelevant stuff here.
            list_of_subscriptions: HashMap::new()
        }
    }
    
    fn register_local_subscriptions(&mut self){
        let closure = |message: Arc<WorkOrderCreated>| { self.handle_work_order_created(message)};
        self.list_of_subscriptions.insert(String::from(WorkOrderCreated::get_message_type_name()), closure);
    }
    
    fn handle_work_order_created(&self, event: Arc<WorkOrderCreated>) {
        println!("Work Order created received");
    }
}

#[async_trait]
impl Actor for WorkOrderManager {
    // The as_any method is used to get a reference to the actor as a dyn Any.
    fn as_any_mut(&mut self) -> &mut dyn Any{
        self
    }

    // The get_address method is used to get the address of the actor.
    fn get_address(&self) -> Arc<ActorAddress>{
        self.actor_address.clone()
    }

    // The get_list_of_subscriptions method is used to get a list of message types that the actor is subscribed to.
    fn get_list_of_subscriptions(&self) -> Vec<&String>{
        self.list_of_subscriptions.keys().collect()
    }

    // The get_behavior_change_notifier method is used to get the behavior change notifier of the actor.
    // The notifier is used to notify interested parties when an actor changes its behavior.
    fn get_behavior_change_notifier(&self) -> Arc<Notify>{
        self.notifier.clone()
    }

    // The get_id method is used to get the unique identifier of the actor.
    fn get_id(&self) -> ActorId{
        self.actor_id.clone()
    }
    
    // The activate method is used to activate the actor.
    async fn activate(&mut self){
        let receiver = self.receiver.clone();
        tokio::task::spawn(async move {
            let mut receiver = receiver.lock().await;
            loop {
                let envelope = receiver.recv().await.unwrap();
                match envelope.as_ref() {
                    Envelope::Event(event) => {
                        // Handle event
                        let message_type = event.get_message_type();
                        let subscription = self.list_of_subscriptions.get(message_type).unwrap();
                        subscription(event.clone());
                    },
                    Envelope::Command(command) => {
                        // Handle command
                        todo!()
                    }
                }
            }
        });
    }
}

Here an example of the implementation of IEvent:

#[derive(Serialize, Deserialize)]
pub struct WorkOrderAborted{
    pub base: EventBase,
    work_order_id: String, 
    assay: String
}

impl WorkOrderAborted{
    pub fn new(origin: &str, version: &str, name: &str, work_order_id: String, assay: String) -> Self{
        Self { base: EventBase::new(origin, version, name, "payload here"), work_order_id, assay }
    }
}

impl ToJsonMessage for WorkOrderAborted{}

impl FromJsonMessage for WorkOrderAborted{}

impl IEvent for WorkOrderAborted{
    fn get_message_type_name(&self) -> &'static str {
        "WorkOrderAborted"
    }
}

I'm getting the IEvent cannot be made into an object and I'm not really sure why is it. I would really appreciate some enlightment here.

Thanks a lot in advance.

This error message comes with an explanation, which you can view with rustc --explain E0038 in the terminal or online here. Feel free to always check these out first.

Your code tries to work with a dyn IEvent trait object, but the IEvent trait has a supertrait that isn’t “object safe”, namely FromJsonMessage, which features a method that is not allowed in trait objects for 2 reasons: it has a generic argument; it doesn’t have a self receiver.

The compiler also calls out the concrete issues, seems that from_json also has an issue.

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/lib.rs:17:8
   |
3  | pub trait IEvent: ToJsonMessage + FromJsonMessage{
   |           ------ this trait cannot be made into an object...
...
17 |     fn to_json(&self) -> String
   |        ^^^^^^^ ...because method `to_json` references the `Self` type in its `where` clause
...
30 |     fn from_json<T>(json: &str) -> T
   |        ^^^^^^^^^ ...because associated function `from_json` has no `self` parameter

that being said, I don’t really understand the point of your FromJsonMessage and ToJsonMessage traits anyways. Both have a default implementation and no indication as to why and for what reason one would ever need to override it. The former furthermore makes no use whatsoever of the Self type.

2 Likes

You cannot create a trait object of any trait that has generic method. Precise rules around object safety can be found in The Rust Reference. Since FromJsonMessage::from_json is a generic function, and FromJsonMessage is a supertrait of IEvent, you cannot create dyn IEvent. You have to either make from_json not generic, or exclude it from trait objects, by adding additional bound where Self: Sized on generic function.

Thanks a lot for looking into my question.

Yes, I saw these reasons from the compiler (sorry, I should have attached them as part of the question).

I think it is fair to ask the question about the usefulness of the From and To Json traits. The point about them having a default implementation: as I'm not an expert in Rust, how else would you make that piece of code reusable so that I don't have to repeat it in each and every new implementation of a Message? I though that was the whole point about default implementations of traits. But I'm interested in other options, definetly.

Ah maybe creating some macro to expand the implementers of IEvent and insert such methods?

As @akrauze mentioned, as a minimal fix, you can mark the offending methods with Self: Sized to remove their availability from any trait object type. Feel free to just follow that and see if you run into more problems with your intended use-case. (You don’t e.g. share any code here that makes use of)

Default implementations are commonly used for cases where some additional methods of a trait are defined in terms of other methods, and/or when only a few types need specific different behavior from the common case. It’s not a strict rule, still outside of these use-cases other approaches are often more convenient, too.

You could consider trying out the extension trait pattern. (E.g. here’s an intro video about it.) This doesn’t help with support for the de-/serializaation on trait objects any more though, anyways – it’d probably rather, similarly to adding Self: Sized restrictions allow object safety, result in this functionality just being unavabilable and that way making the compiler happy so far (until you try to use it):

If your goal is to actually use such serialization and deserialization functionality for trait objects, simple object safety won’t help much, because Arc<dyn IEvent> will not fulfill the Deserialize or Serialize bounds. Serialization you might be able to work around with a properly object-safe method (e.g. a blanket implementation using Serialize, then remove the Self: Serialize restriction, it’s already object safe). Deserialization is trickier though; you might even need help from something like the typetag crate.

1 Like

The basic unit of reusable code is a function — not a trait function, not a method, just a function. You don't need to define a new trait to have a new function. Not everything needs to involve Self/self.

8 Likes
mod json {
  pub fn from_json<T>(json: &str)
  where T: serde::DeserializeOwned
  {
    serde_json::from_str(json).expect("valid JSON")
  }

  pub fn to_json<T>(value: &T)-> String
  where T: serde::Serialize
  {
    serde_json::to_string(value).expect("can serialize value")
  }
}

Invoked as either ...::json::to_json(foo) or ...::json::from_json(some_str). I might quibble about the use of unwrap/expect; my preference would be to return errors, in which case I might eliminate these functions entirely, but you've opted to go for panics, so this is how I'd do that.

At the call site, you'll need to statically know what type you're serializing or deserializing. While you can define a serialization trait that would let you serialize arbitrary instances of dyn T for some suitable trait, one that lets you deserialize is fiendishly hard to define. It's generally easier to do the deserialization against a set of known concrete structures (or against one single structure, likely using an enum).

1 Like

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.