0
votes

I am building a Raft consensus algorithm middleware for Rust with a similar architecture to copycat. I wrote a macro for developers to define a scheme for commands. The macro generates a trait A with a dispatch function for user defined commands, which requires the developer to implement a struct for its behavior and an encoder/decoder which is irrelevant to this topic.

The trait objects register with a Server object to make it work. Because trait A is dynamically generated, I have to define another trait B as parent trait for A. Server works with B to invoke the dispatch function defined in A.

I tried different combination but none of them works.

trait B {
    fn dispatch(&mut self, fn_id: u64) -> Vec<u8>;
}

// macro generated trait
trait A: B {
    fn a(&self) -> Vec<u8>;
    fn b(&self) -> Vec<u8>;
    fn dispatch(&mut self, fn_id: u64) -> Vec<u8> {
        match fn_id {
            1 => a(),
            2 => b(),
            _ => {}
        }
    }
}

Unfortunately, the dispatch function in A will not implement dispatch in B. When I implement the structure for A, the compiler still asked to implement dispatch for B.

I also tried to move dispatch in trait B to another trait as the parent trait for B, but the trait cannot implement for A.

I have also reviewed https://github.com/rust-lang/rfcs/issues/1024. It seems the issue is open but have been ditched. Is there any alternative way to make this pattern work?

1
Your question is not very clear. Why not just implement B at the same time you are expanding the macro that creates the trait A?Shepmaster
@Shepmaster because there will be more traits like A generated by the macro the server need a common trait to invoke dispatchShisoft

1 Answers

6
votes

You don't need B to be a supertrait of A at all. You can instead add a blanket implementation of B for any T: A. I think this closer to the intent.

Paraphrasing the Rust code:

trait A: B {

"You can implement A, but only if you've implemented B."

trait A {
    //...
}
impl<T: A> B for T {

"If you implement A, here's a free implementation of B."

Here's the full version:

trait B {
    fn dispatch(&mut self, fn_id: u64) -> Vec<u8>;
}

// macro generated trait
trait A {
    fn a(&self) -> Vec<u8>;  // added &self
    fn b(&self) -> Vec<u8>;
}

impl<T: A> B for T {
    fn dispatch(&mut self, fn_id: u64) -> Vec<u8> {
        match fn_id {
            1 => self.a(),
            2 => self.b(),
            _ => panic!(),   // Needed so that match cases are exhaustive
        }
    }
}

struct S {}

impl A for S {
    fn a(&self) -> Vec<u8> {
        unimplemented!()
    }
    fn b(&self) -> Vec<u8> {
        unimplemented!()
    }
}

Playground