Serde + Generic Functions

I have some code like so:

             e @ EventKey::Event1 => {
                 json::from_value::<Event1>(obj)
                     .map_err(|err| EventError::DecodeEventError(e.clone(),err,None))
                     .map(|event| {
                         EventMessage {
                             header: hdr,
                             key: event.md5.0.to_owned(),
                             event
                         }})

however if I try to add it to a function and make the Event1 a generic type:

             e @ EventKey::Event1 => {
                 self.some_fun::<Event1>(obj)
                 ...

             fn some_fun<P>(&self, obj: EventObj) {
                 json::from_value::<P>(obj)
                     .map_err(|err| EventError::DecodeEventError(e.clone(),err,None))
                     .map(|event| {
                         EventMessage {
                             header: hdr,
                             key: event.md5.0.to_owned(),
                             event
                         }})
            }

I get an error like so:

  --> src/parser.rs:70:32
   |
70 |                     key: event.md5.0.to_owned(),
   |                                ^^^

error: aborting due to previous error

Basically I am trying to build a parser for multiple event types after they have been decoded and would like to avoid duplicate code however it seems by making the type generic it doesn't know how to handle the struct.

In this context, event's type is a generic type parameter, and in Rust you cannot perform field access on an abstract type (yet).

You have two solutions:

  • encoding the common behavior of "getting that field" within a method of some trait

    trait GetMd5 {
        fn md5 (self: &'_ Self)
          -> &'_ ... // type of md5 field here
        ;
    }
    impl GetMd5 for Event1 {
        #[inline]
        fn md5 (self: &'_ Self)
          -> &'_ ... // type of md5 field here
        {
            &self.md5
        }
    }
    
    fn some_fun<P>(&self, obj: EventObj)
    where
        P : GetMd5,
    {
        ...
        key: event.md5().0.to_owned(),
    }
    
    • slightly cumbersome, but the recommended way to go
  • another form of polymorphism: macro-based "duck typing"

    // to be defined "in the middle" of your function, when `obj` is in scope
    macro_rules! EventKey_match_all {(
        $( $EventN:ident ),* $(,)?
    => (match ... {
        $(
            e @ EventKey::$EventN => {
                 json::from_value::<$EventN>(obj)
                     .map_err(|err| EventError::DecodeEventError(e.clone(), err, None))
                     .map(|event| EventMessage {
                         header: hdr,
                         key: event.md5.0.to_owned(),
                         event,
                     }})
            },
        )*
    })}
    
    EventKey_match_all!( Event1, Event2, ... )
    
    • this is better if you end up having many places where you want to factor out code, and defining all the right traits and method getters for it to work leads to writing more code than the generic function saves you from typing

Ok now I might be trying something insane here but is it possible with macros to have certain events change certain fields?

For eample:

Event1 needs event.md5.0.to_ownedd()

however Event2 needs event.sensor.0.to_owned()

You can instead of calling the macro like

macro_name!(
    Event1,
    Event2,
)

Call it like:

macro_name!(
    Event1 => md5,
    Event2 => sensor,
)

To do this, the "input" part of the macro should be:

macro_rules! macro_name {(
    $(
         $EventN:ident => $field:ident
    ),* $(,)?
) => (
    match ... { ... }
)}

using event.$field instead of event.md5
For more info about macros, you can look for DanielKeep's little book of Rust macros

2 Likes