Abstracting serialization of specific state types

Hello! I'm trying to make a generic serialization/deserialization of a state transition logic. The code looking something like below:

    pub trait StateTransition<STATE>
    where
      STATE: Serialize + DeserializeOwned,
    {
      fn act(&self, state: STATE) -> Result<STATE>;
    }

    fn act_generic<STATE>(state: String, actor: Box<dyn StateTransition<STATE>>) -> Result<String>
    where
      STATE: Serialize + DeserializeOwned,
    {
      let state: STATE = serde_json::from_str(&state)?;
      let new_state: STATE = actor.act(state)?;
      Ok(serde_json::to_string(&new_state)?)
    }

    struct StateTransition1 {};
    struct StateTransition2 {};

    #[derive(Serialize, Deserialize)]
    struct State1 {
      x: u32,
    };

    #[derive(Serialize, Deserialize)]
    struct State2 {
      y: u32,
    };

    impl StateTransition<State1> for StateTransition1 {
      fn act(&self, state: State1) -> Result<State1> {
        Ok(State1 { x: state.x + 1 })
      }
    }
    impl StateTransition<State2> for StateTransition2 {
      fn act(&self, state: State2) -> Result<State2> {
        Ok(State2 { y: state.y + 2 })
      }
    }

    println!(
      "{:?}",
      act_generic(
        r#"{ "x": 1, "y": 2}"#.to_string(),
        Box::new(StateTransition1 {})
      )
    );
    println!(
      "{:?}",
      act_generic(
        r#"{ "x": 1, "y": 2}"#.to_string(),
        Box::new(StateTransition2 {})
      )
    );

This works nicely. But the question is how could I return dyn StateTransition<???> from a factory function. Or which is quite the same save multiple dyn StateTransition instances to a vector. So I could act_generic in a loop E.g.

    let transitions: Vec<Box<dyn StateTransition<???>>> =
      vec![Box::new(StateTransition1 {}), Box::new(StateTransition2 {})];

As a first step to someone answering this question, I made your example code readable and compilable (i.e. remove incorrect semicolons and add imports and an fn main, apply rustfmt, add syntax highlighting)

use serde::{Serialize, Deserialize, de::DeserializeOwned};
use serde_json::Result;

pub trait StateTransition<STATE>
where
    STATE: Serialize + DeserializeOwned,
{
    fn act(&self, state: STATE) -> Result<STATE>;
}

fn act_generic<STATE>(state: String, actor: Box<dyn StateTransition<STATE>>) -> Result<String>
where
    STATE: Serialize + DeserializeOwned,
{
    let state: STATE = serde_json::from_str(&state)?;
    let new_state: STATE = actor.act(state)?;
    Ok(serde_json::to_string(&new_state)?)
}

struct StateTransition1 {}
struct StateTransition2 {}

#[derive(Serialize, Deserialize)]
struct State1 {
    x: u32,
}

#[derive(Serialize, Deserialize)]
struct State2 {
    y: u32,
}

impl StateTransition<State1> for StateTransition1 {
    fn act(&self, state: State1) -> Result<State1> {
        Ok(State1 { x: state.x + 1 })
    }
}
impl StateTransition<State2> for StateTransition2 {
    fn act(&self, state: State2) -> Result<State2> {
        Ok(State2 { y: state.y + 2 })
    }
}

fn main() {
    println!(
        "{:?}",
        act_generic(
            r#"{ "x": 1, "y": 2}"#.to_string(),
            Box::new(StateTransition1 {})
        )
    );
    println!(
        "{:?}",
        act_generic(
            r#"{ "x": 1, "y": 2}"#.to_string(),
            Box::new(StateTransition2 {})
        )
    );
}

(playground)

Feel free to read the pinned post about syntax highlighting.

2 Likes

Thanks Steffahn! I've copied from a test method body hence the semicolons. But playground links looks much nicer.

After thinking of it for a while I've found a solution which leverages:

  1. Associated types
  2. Blanket trait implementation
  3. Trait objects

Overall associated trait types were the key to find a solution.

Here is working code

pub trait StateTransition {
    type STATE: Serialize + DeserializeOwned;

    fn act(&self, state: Self::STATE) -> Result<Self::STATE>;
}

pub trait GenericStateTransition {
    fn act_generic(&self, state: String) -> Result<String>;
}

impl<T: StateTransition> GenericStateTransition for T {
    fn act_generic(&self, state: String) -> Result<String> {
        let state: T::STATE = serde_json::from_str(&state)?;
        let new_state = self.act(state)?;
        Ok(serde_json::to_string(&new_state)?)
    }
}

struct StateTransition1 {}
struct StateTransition2 {}

#[derive(Serialize, Deserialize)]
struct State1 {
    x: u32,
}

#[derive(Serialize, Deserialize)]
struct State2 {
    y: u32,
}

impl StateTransition for StateTransition1 {
    type STATE = State1;
    fn act(&self, state: State1) -> Result<State1> {
        Ok(State1 { x: state.x + 1 })
    }
}
impl StateTransition for StateTransition2 {
    type STATE = State2;
    fn act(&self, state: State2) -> Result<State2> {
        Ok(State2 { y: state.y + 2 })
    }
}

fn main() {
    let actors: Vec<Box<dyn GenericStateTransition>> =
        vec![Box::new(StateTransition1 {}), Box::new(StateTransition2 {})];

    for actor in actors {
        println!(
            "{:?}",
            actor.act_generic(r#"{ "x": 1, "y": 2}"#.to_string(),)
        );
    }
}

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.