Using TypeIds to look up functions at runtime

For converting a Markdown syntax tree to HTML, I need something more flexible than traits (I want various parties to be able to customize the rendering for any syntax node).

This is a playground with a simplified version of my code.

Excerpt:

pub trait Renderable {}
pub type RenderFunc = fn(&mut Renderer, &Box<dyn Any>);

pub struct Renderer {
    data: String,
    render_funcs: HashMap<TypeId, RenderFunc>,
}
impl Renderer {
    pub fn render(&mut self, data: &Box<impl Renderable + 'static>) {
        let key = (**data).type_id();
        let func = self.render_funcs.get(&key).unwrap();
        func(self, &((*data) as Box<dyn Any>)); // (A)
    }
}

Problems:

  • If I leave parameter data of .render() as is, I can’t invoke it with an argument whose type is Box<dyn Renderable> (see playground link above).
    • If I change the type of data to Box<impl Renderable + 'static + ?Sized>, I can invoke the method, but the cast to Box<dyn Any> doesn’t work.
  • The function invocation in line A doesn’t work either.

Any help appreciated!

I poked at your playground a bit, and got a version that passes your tests.

The main trick I used is defining a method in Renderable to get the dyn Any you need for downcasting. The second trait lets you provide a blanket implementation for all Sized types that will then be included in the vtable for dyn Renderable; without that, each implementor of Renderable would have to define as_any itself.

pub trait Renderable: AsAny {}

pub trait AsAny {
    fn as_any(&self)->&dyn Any;
}

impl<T:Any> AsAny for T {
    fn as_any(&self)->&dyn Any { self as &dyn Any }
}

I then removed some of the superfluous Boxing where you're really just passing references:

pub type RenderFunc = fn(&mut Renderer, &dyn Any);

// ...

impl Renderer {
    pub fn render(&mut self, data: &(impl Renderable + ?Sized + 'static)) {
        let key = data.as_any().type_id();
        let func = self.render_funcs.get(&key).unwrap();
        func(self, data.as_any());
    }
}

// ...
mod tests {
    #[test]
    fn rendering() {
        let mut renderer = Renderer::new();

        fn render_chunks(renderer: &mut Renderer, chunks_any: &dyn Any) {
            let chunks = chunks_any.downcast_ref::<Chunks>().unwrap();
            for chunk in &chunks.chunks {
                renderer.render(&**chunk);
            }
            ()
        }
        renderer.set_render_func::<Chunks>(render_chunks);

        // ...
    }
}
2 Likes

I haven't reviewed its code, but typemap exists.

1 Like

If you're willing to use a little more indirection, you can also take care of the downcasting for the downstream programmer, preventing any inadvertent type mismatches:

    pub fn set_render_func<T: 'static>(&mut self, render_func: impl 'static+Fn(&mut Renderer, &T)) {
        self.render_funcs.insert(
            TypeId::of::<T>(),
            Rc::new(move |r,a| render_func(r, a.downcast_ref().unwrap()))
        );
    }
1 Like

Very helpful, thanks a lot! I like the idea of doing the casting for the registered functions.

In principle, I’m looking for extensible multiple dynamic dispatch. The create double-dyn almost gets me there but its multifunctions can’t be extended later. Thus, I’ll be using my code with the fixes proposed by @2e71828.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.