For quite some time, I've had the goal of achieving an API like this:
struct GetEvent;
fn handler(_event: &mut GetEvent) {}
pub fn main() {
let mut server = Server::default();
server.handle(handler);
server.emit(PostEvent);
}
The names and types are just examples, this actual program has nothing to do with HTTP
The last two days, I've returned to the problem once again. And I felt so close a couple of times, but I just can't quite get it. I'm really pulling my hair out.
Below you will find the things I've tried, you can skip all of this, if you immediately know what to do and are wiser than me :D
First, the basic structure is available here. This one compiles, so the relevant methods have been stubbed with todo!()
.
So, here we go:
Naively, I just looked up on how to use any, and created this hash map:
struct Server {
handlers: HashMap<TypeId, Box<dyn Handler<dyn Any>>>,
}
fn handle<E>(&mut self, handler: impl Handler<E>) {
self.handlers.insert(TypeId::of::<E>(), Box::new(handler));
}
However:
--> src/main.rs:26:49
|
26 | self.handlers.insert(TypeId::of::<E>(), Box::new(handler));
| ^^^^^^^^^^^^^^^^^ the trait `for<'a> Fn(&'a mut (dyn Any + 'static))` is not implemented for `impl Handler<E>`, which is required by `impl Handler<E>: Handler<(dyn Any + 'static)>`
|
I honestly don't have any idea what this means. But I'm assuming, this just doesn't work.
struct Server {
handlers: HashMap<TypeId, Box<dyn Any>>,
}
fn handle<E: 'static>(&mut self, handler: impl Handler<E>) {
self.handlers.insert(TypeId::of::<E>(), Box::new(handler));
}
And this... compiles! Victory! Well sadly not. After reading about down casting for a good hour, I got a runtime error!
fn emit<E: 'static>(&mut self, mut event: E) {
let Some(handler) = self.handlers.get_mut(&TypeId::of::<E>()) else {
println!("did not find handler...");
return;
};
let handler = handler.downcast_ref::<Box<dyn Handler<E>>>().unwrap();
handler.handle(&mut (event as E));
}
thread 'main' panicked at src/main.rs:36:69:
called `Option::unwrap()` on a `None` value
But I think I understand why this isn't working. The registration function, fn handle<E: 'static>(...)
, will be turned into static dispatch, so the type won't be Box<dyn Handler<E>>
, but: alloc::boxed::Box<rust_test::handler>
. Which is the static function pointer of my handler.
Handler
trait first?fn handle<E: 'static>(&mut self, handler: impl Handler<E>) {
let handler = Box::new(handler) as Box<dyn Handler<E>>;
self.handlers.insert(TypeId::of::<E>(), handler);
}
No! Because... err, no!
= note: expected struct `Box<(dyn Any + 'static)>`
found struct `Box<dyn Handler<E>>`
Aha! Require Any + 'static
as base trait for handler? Apparently not:
cannot cast `dyn Handler<E>` to `dyn Any`, trait upcasting coercion is experimental
*No code or any other content has been directly generated by AI. Everything has been transformed and was vetted by me.
"Isn't possible... something type erasing... bla bla".
Okay, sure:
trait HandlerErased {
fn handle(&self, event: &mut dyn Any);
}
impl<E, H: Handler<E>> HandlerErased for H {
fn handle(&self, event: &mut dyn Any) {
// Check if correct type
todo!();
}
}
Whilst I'm not too much of a fan of this code, seems a little the wrong way around, to have a check for the correct type in here. At this point, it should be the correct type of handler anyway. But I'm getting desperate, so send it!
As always... doesn't work, only hallucinates more when confronted:
error[E0207]: the type parameter `E` is not constrained by the impl trait, self type, or predicates
--> src/main.rs:24:6
|
24 | impl<E, H: Handler<E>> HandlerErased for H {
| ^ unconstrained type parameter
I've tried some additional things, but I don't think any of them are noteworthy. And yes, I know I can just use a gigantic Enum, but that is what I'm doing now, and the API is quite messy. The goal of this is, that the user of the API can just pick the events they want to handle.
Furthermore, this will potentially allow me to detect (without being a macro wizard2) and generate some values depending on it (server capabilities, in case you're interested).
I've read these resources and more:
Fn
in the handler, and I want it to be trait based. I also failed to understand what this Option
business was about.PS:
- What is this about...
- I would kindly ask edit warriors to refrain from removing context, and or sterilizing this question. Thank you!
This can be done but requires a few boxing layers -- you need one layer to unify the different Handler<E>
implementations (Box<dyn Handler<E>>
) and you need another to unify those boxes to erase the E
type behind Any
(Box<dyn Any>
).
First, the server is defined like this:
#[derive(Default)]
struct Server {
handlers: HashMap<TypeId, Box<dyn Any>>,
}
Adding a handler requires boxing it twice. We'll let the caller take care of the first box by accepting a boxed handler.
fn handle<E: 'static>(&mut self, handler: Box<dyn Handler<E>>) {
self.handlers.insert(TypeId::of::<E>(), Box::new(handler));
}
Note we have to bound E: 'static
because TypeId
does not encode lifetimes since the possible number of lifetimes is technically unbounded.
To use this, we have to downcast the Any
back to a Box<dyn Handler<E>>
. Here I just unwrap()
the result, because errors are unexpected and indicate an error in functions that manipulate the handlers
map, therefore a panic is appropriate.
fn emit<E: 'static>(&mut self, mut event: E) {
if let Some(boxed) = self.handlers.get(&TypeId::of::<E>()) {
boxed
.downcast_ref::<Box<dyn Handler<E>>>()
.unwrap()
.handle(&mut event);
}
}
Note there are some unsafe
tricks we can use to avoid the double-boxing, but I'd only reach for those if performance is actually a problem with this approach.
If you are open to changing Handler
from a generic trait to one with an associated type, we can avoid the double-boxing by creating a type-erased version of the Handler
trait that accepts a &mut dyn Any
argument.
First, the new trait definition:
trait Handler {
type Event;
fn handle(&self, event: &mut Self::Event);
}
We can't make a blanket implementation for Fn
now because it will be ambiguous; theoretically Fn
could be implemented multiple times by one type in the future, even though this is currently not the case. Therefore, we need to create a wrapper struct around Fn
that knows a specific event type to use so that we can implement Handler
on it.
struct FnHandler<F, E> {
func: F,
_e: PhantomData<fn() -> E>,
}
impl<F, E> FnHandler<F, E> {
pub fn new(func: F) -> Self {
Self {
func,
_e: Default::default(),
}
}
}
impl<F, E> Handler for FnHandler<F, E>
where
F: Fn(&mut E),
{
type Event = E;
fn handle(&self, event: &mut E) {
(self.func)(event)
}
}
Note this is a zero-cost abstraction, but it does require that you use FnHandler::new
to wrap Fn
s so that they implement Handler
. However, this allows you to create other types that implement Handler
.
Now we need a type-erased version of Handler
that we can box handlers into.
impl<T> ErasedHandler for T
where
T: Handler,
T::Event: 'static,
{
fn handle(&self, event: &mut dyn Any) {
T::handle(self, event.downcast_mut::<T::Event>().unwrap())
}
}
Finally, we just need to update the rest of the Server
to use this trait. Note that ErasedHandler
can be a private implementation detail, as it's only used internally by Server
.
#[derive(Default)]
struct Server {
handlers: HashMap<TypeId, Box<dyn ErasedHandler>>,
}
impl Server {
fn handle<E: 'static>(&mut self, handler: impl Handler<Event = E> + 'static) {
self.handlers.insert(TypeId::of::<E>(), Box::new(handler));
}
fn emit<E: 'static>(&mut self, mut event: E) {
if let Some(handler) = self.handlers.get(&TypeId::of::<E>()) {
handler.handle(&mut event);
}
}
}
I think this gives you what you want at the cost of needing to manually wrap handler functions in a FnHandler
. If you wanted to you could provide a helper function on Server
to do this wrapping for you, such as:
fn handle_fn<E: 'static>(&mut self, handler: impl Fn(&mut E) + 'static) {
self.handle(FnHandler::new(handler));
}