I know, quite the use case.
I am trying to build a plugin API for my application where the contract between plugins and the main executer are defined with a trait. I will use the stable_abi crate to achieve this but for now I am using libloading with Rust's built in FFI capabilities.
At its most basic I have a dylib crate (the plugin) and a bin crate (the executor). The executor consumes the dylib and calls methods on it.
The most basic example is this:
/plugin
/executor
/target
/debug
executor
plugin.so
// plugin/src/lib.rs
#[no_mangle]
pub extern fn add(left: usize, right: usize) -> usize {
left + right
}
// executor/src/main.rs
fn main() {
let exe_path = std::env::current_exe().unwrap();
let exe_dir = exe_path.parent().unwrap();
let lib_path = exe_dir.join("libplugin.so");
unsafe {
let lib = libloading::Library::new(&lib_path).unwrap();
let add: libloading::Symbol<unsafe fn(usize, usize) -> usize> = lib.get(b"add").unwrap();
println!("{}", add(1, 1));
}
}
I then progressively add layers to it, moving towards a trait with methods that return futures:
- Working: "add()" returns a future
- Working: "add()" returns a future within a tokio::task
- Working: Struct "Adder.add()" returns a future within a tokio::task
- Failing: Trait "Adder.add()" with an implementation that returns a future from a tokio::task
Tokio fails with:
thread '<unnamed>' panicked at c/src/lib.rs:12:13:
there is no reactor running, must be called from the context of a Tokio 1.x runtime
This failure occurs in tokio::task::spawn within the AddMachine struct, but only when the .add() method is called when it's in the form of a Box<dyn Adder>.
Why is this and is there a way around it?