Designing a Rust -> Rust plugin system
Edit: I'm not interested in IPC, wasm, or using any other languages for the plugins such as lua. I'm either making it all in Rust or not doing this project at all.
Hello,
I'm trying to make a project that would be very minimal in itself, but would have a very powerful Rust plugin system and most of the functionality would be implemented by plugins. So I want the plugins to be as efficient as possible. I'm having some trouble thinking of the best way to do what I want, and at this point I'm not even sure if it's possible anymore.
So, my program is asynchronous, and the plugins should be async too. At first I tried making a dynamic plugin system (.so, or .dll for windows) where each plugin would send it's main task future to the host via async_ffi and then would handle events via an mpsc channel. At first this seemed like a good idea, but as I was implementing it I slowly realised that it wasn't as good as I thought it would be, mainly because of the fact that the plugins weren't runtime-aware (tokio, in my case) and were using just plain futures, which meant that:
- If a plugin wanted to actually do something async they would need to spin up their own reactor, and a separate reactor for each plugin doesn't sound very good, even though I don't know how inefficient it is in reality.
- Can't really use any async crate, since most of them require a tokio (or async_std) runtime.
My next idea was to make the "plugin system" (not sure if it's the right term on this idea) static. I imagined it something like this: you would drop some archived rust code in a .tar to a folder, then compile the project and build.rs would extract them and add them to the project tree, and in the end you would have a single binary with all the "plugins". Don't really like this idea because of the lack of dynamysm, though then I wouldn't have to do anything unsafe and FFI and the whole structure would become simpler. Anyway I couldn't find a way to implement something like that, because each plugin may have it's own dependencies which would need to be added to the main project, but I didn't find a way to add dependencies from build.rs.
So the last and my most recent idea was to make an extension to tokio, that would let me pass a Handle to the runtime through FFI to the plugins and let them use it. I imagine that would mean making FFI-safe versions of several tokio internal structs and a VTable (kind of similar to the implementation of async_ffi). Then the plugins would be able to enter the FFI runtime, reuse the reactors and do everything as if they created the runtime themselves. That would mean that they could use async crates too, as long as you modified the crates to use the modified version of tokio. This appeared to be non trivial and I haven't made a PoC yet, and I wanted to hear the opinion of others before I take on this.
So, what are your thoughts?