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
.