5

I have a trait that looks something like this:

trait Handler<C> {
    fn handle(&self, msg: &Message, connection: &mut C);
}

Instances are supposed to be chained like you would chain middlewares for HTTP handlers:

let handler = FirstHandler {
     next: SecondHandler {
         next: FinalHandler {},
     },
};

Each handler type can impose additional constraints on the type C:

trait ConnectionThatWorksWithFirstHandler {
    ...
}

struct FirstHandler<C: ConnectionThatWorksWithFirstHandler, H: Handler<C>> {
    next: H,
    _phantom: PhantomData<C>,
}

As you can see here, I need a PhantomData<C> to avoid error E0392 (parameter C is never used). However, PhantomData is semantically wrong because the handlers are not holding instances of C. This is ugly. For example, I manually have to provide the correct Sync/Send trait implementations:

unsafe impl<C: ConnectionThatWorksWithFirstHandler, H: Handler<C>> Send for Handler<C, H> where H: Send {}
unsafe impl<C: ConnectionThatWorksWithFirstHandler, H: Handler<C>> Sync for Handler<C, H> where H: Sync {}

The auto trait implementations would have an additional where C: Send/Sync bound which is not appropriate here.

Is there an alternative to PhantomData that allows me to encode the relation between FirstHandler<C> and C such that the Rust compiler is happy and I don't need more unsafe code?

I'm not looking for associated types. The handler trait and its implementors are defined in a library, and the concrete type for C is defined in the application consuming the library, so the concrete type C cannot be defined by the handlers' trait implementations.

The idea with this design is to allow the chain of handlers to accumulate all the trait bounds for C that are required in the handler chain, so that when I have the handler variable as shown in the second snippet, then the implied trait bound is C: ConnectionThatWorksWithFirstHandler + ConnectionThatWorksWithSecondHandler + ConnectionThatWorksWithFinalHandler.

Stefan Majewsky
  • 5,427
  • 2
  • 28
  • 51
  • [Specify `Fn` trait bound on struct definition without fixing one of the `Fn` parameters](https://stackoverflow.com/questions/50671177/specify-fn-trait-bound-on-struct-definition-without-fixing-one-of-the-fn-par) is a similar question that you may find useful to read – trent Sep 05 '18 at 18:41
  • And [“parameter `'a` is never used” error when 'a is used in type parameter bound](https://stackoverflow.com/q/40484154/3650362) is also similar but with a lifetime parameter instead of a type. – trent Sep 05 '18 at 18:44

1 Answers1

8

There is no need to enforce the constraints on the inner handler at the definition of the struct. You can delay them until you implement the Handler trait for FirstHandler.

trait Handler<C> {
    fn handle(&self, msg: &Message, connection: &mut C);
}

struct FirstHandler<H> {
    next: H
}

impl<C, H> Handler<C> for FirstHandler<H>
where
    H: Handler<C>,
    C: ConnectionThatWorksWithFirstHandler,
{
    fn handle(&self, msg: &Message, connection: &mut C) {
        //...
    }
}
Markus Klein
  • 1,202
  • 12
  • 10