Home > Back-end >  Rust generic type on closure
Rust generic type on closure

Time:02-01

I would like to make a generic structure with closures, trying to model the finite state machine with Rust. A finite state machine holds two mappings(closures in this case);

  • update: input X state => state
  • output: input X state => output

Here is my first attempt.

pub struct Fsm<A, S, B, U, O>
where
    A: Clone,
    S: Clone,
    B: Clone,
    U: Fn(&A, &S) -> S   Clone,
    O: Fn(&A, &S) -> B   Clone,
{
    update: U,
    output: O,
    state: S,
    _a: PhantomData<A>,
    _b: PhantomData<B>,
}

impl<A, S, B, U, O> Fsm<A, S, B, U, O>
where
    A: Clone,
    S: Clone,
    B: Clone,
    U: Fn(&A, &S) -> S   Clone,
    O: Fn(&A, &S) -> B   Clone,
{
    pub fn new(update: U, output: O, state: S) -> Self {
        Fsm {
            update: update,
            output: output,
            state: state,
            _a: PhantomData,
            _b: PhantomData,
        }
    }

    pub fn get_state(&self) -> S {
        self.state.clone()
    }

    fn put_state(&mut self, state: &S) -> () {
        self.state = state.to_owned();
    }

    pub fn react(&mut self, input: &A) -> B {
        let new_state = (self.update)(input, &self.get_state());
        self.put_state(&new_state);
        (self.output)(input, &new_state)
    }
}

impl<A, S1, B, U1, O1> Fsm<A, S1, B, U1, O1>
where
    A: Clone,
    S1: Clone,
    B: Clone,
    U1: Fn(&A, &S1) -> S1   Clone,
    O1: Fn(&A, &S1) -> B   Clone,
{
    pub fn product<C, S2, U2, U3, O2, O3>(
        self,
        fsm: Fsm<B, S2, C, U2, O2>,
    ) -> Fsm<A, (S1, S2), C, U3, O3>
    where
        S2: Clone,
        C: Clone,
        U2: Fn(&B, &S2) -> S2   Clone,
        U3: Fn(&A, &(S1, S2)) -> (S1, S2)   Clone,
        O2: Fn(&B, &S2) -> C   Clone,
        O3: Fn(&A, &(S1, S2)) -> C   Clone,
    {
        Fsm::new(
            |x, (s1, s2)| {
                let new_s1 = (self.update)(x, s1);
                (new_s1, (fsm.update)(&(self.output)(x, &new_s1), s2))
            },
            |x, (s1, s2)| {
                let new_s1 = (self.update)(x, s1);
                let new_s2 = (fsm.update)(&(self.output)(x, &new_s1), s2);
                (fsm.output)(&(self.output)(x, &new_s1), &new_s2)
            },
            (self.state, fsm.state),
        )
    }
}

However, it has two annoying parts;

  1. If one does not put _a, and _b compiler complains. A, B are there to relate the U and O.
  2. the product part make compile error like following;
arguments to this function are incorrect
expected type parameter `U3`
          found closure `[closure@src/lib.rs:104:13: 104:26]`
every closure has a distinct type and so could not always match the caller-chosen type of parameter `U3`
expected type parameter `O3`
          found closure `[closure@src/lib.rs:108:13: 108:26]`
every closure has a distinct type and so could not always match the caller-chosen type of parameter `O3`rustcClick for full compiler diagnostic

How could I resolve these issues?

CodePudding user response:

Your first concern can be easily solved by just using the constructor you defined Fsm::new where you want to create a new instance and if you want to pattern match you can use something like Fsm{ update, ..} as pattern and ignore everything not specified. Also with the bound O: Fn(&A, &S) -> B Clone on the struct _b is just not needed at all.

The first set of errors can be overcome by using existential types instead of universal generics for U3 and O3:

pub fn product<C, S2, U2, O2>(
    self,
    fsm: Fsm<B, S2, C, U2, O2>,
) -> Fsm<
    A,
    (S1, S2),
    C,
    impl Fn(&A, &(S1, S2)) -> (S1, S2)   Clone,
    impl Fn(&A, &(S1, S2)) -> C   Clone,
>
where
    S2: Clone,
    C: Clone,
    U2: Fn(&B, &S2) -> S2   Clone,
    O2: Fn(&B, &S2) -> C   Clone,
{
    /*...*/
}

But now you get a bunch of borrow checker errors.

You can resolve most of them by requiring 'static closures for your types. If that's not flexible enough you'd have to replace 'static with appropriate named lifetimes everywhere in the snippet below. The rest can be resolved by adding clone and move so we create necessary copies of the Fn arguments and move them.

impl<A, S1, B, U1, O1> Fsm<A, S1, B, U1, O1>
where
    A: Clone,
    S1: Clone,
    B: Clone,
    U1: Fn(&A, &S1) -> S1   Clone   'static,
    O1: Fn(&A, &S1) -> B   Clone   'static,
{
    pub fn product<C, S2, U2, O2>(
        self,
        fsm: Fsm<B, S2, C, U2, O2>,
    ) -> Fsm<
        A,
        (S1, S2),
        C,
        impl Fn(&A, &(S1, S2)) -> (S1, S2)   Clone,
        impl Fn(&A, &(S1, S2)) -> C   Clone,
    >
    where
        S2: Clone,
        C: Clone,
        U2: Fn(&B, &S2) -> S2   Clone   'static,
        O2: Fn(&B, &S2) -> C   Clone   'static,
    {
        Fsm::new(
            {
                let self_update = self.update.clone();
                let fsm_update = fsm.update.clone();
                let self_output = self.output.clone();
                move |x, (s1, s2)| {
                    let new_s1 = (self_update)(x, s1);
                    (new_s1.clone(), (fsm_update)(&(self_output)(x, &new_s1), s2))
                }
            },
            move |x, (s1, s2)| {
                let new_s1 = (self.update)(x, s1);
                let new_s2 = (fsm.update)(&(self.output)(x, &new_s1), s2);
                (fsm.output)(&(self.output)(x, &new_s1), &new_s2)
            },
            (self.state, fsm.state),
        )
    }
}
  • Related