Dynamic load plugins in Rust

The gist of the problem I want to solve is:

  • x86_64 linux only, don't care about other platforms
  • there is a crate "plugin_trait" , which defines some traits
  • there is a crate "app", which imports the "plugin_trait" crate" and runs an app

Now, while the "app" is running, we can build a new plugin by using the "plugin_trait" crate, building a *.so, which the currently running app can then dynamically load (and hopefully also unload un-used plugins).

Is @Michael-F-Bryan 's Michael-F-Bryan the state of the art for doing dynamic plugins in Rust ?

I would suggest referring to this article from Mario Ortiz Manero:

My old Plugins in Rust article is kinda unsound because it passes trait objects across the FFI boundary and not using #[repr(C)] appropriately.

Originally it was assumed that this would be fine as long as you pin the compiler version, but in the 3 years since that article was written a lot of work has been done by the unsafe code workgroup. The biggest difference is that you can no longer rely on different runs of the compiler (even if it's the exact same version) to generate compatible code unless you are explicitly opting into ABI compatibility with #[repr(C)] and explicit calling conventions.

2 Likes

I would have never guessed this.

Does this mean this code GitHub - AndrewGaspar/rust-plugin-example: An example of implementing a plugin model in Rust , which looks perfectly reasonable to me, might actually be broken ?

I mean, it works in practice so that's something, but the nice thing about undefined behaviour is that "working exactly like you expect" is just as likely an outcome.

I am not sure if I stated my question correctly.

  1. I believe your claim of: it is possible to compile 'app-server' and 'plugin' with same compiler version on some machine, and get incompatibility -- i.e. the app-server segfaulting on trying to load the so.

  2. I find [1] a bit surprising, but believable.

  3. I am not sure if the github repo above suffers from [1].

I am asking, in your opinion, whether the github repo above suffers from [1].

You probably don't want to go down this path, going by the mess amos got himself into:

(Scroll down to "What can prevent dlclose from unloading" of that link doesn't take you there and you don't have half an hour)

Essentially, it is impossible to unload .so's under glibc if there's any global objects in it, which there will be in Rust. They use a terrible terrible crime to force it to happen, but it's not something that should get anywhere near production.

I'd recommend using child processes and IPC of some sort. You can use shared memory if performance is absolutely critical.

1 Like

Yes it does. You can see the host lookup a function pointer which accepts a &mut dyn PluginRegistrar.

It looks like that repo was written based directly on my dodgy article :disappointed:

However, it is still quite possible to create FFI-safe trait objects (there's also a proc-macro for generating the boilerplate).

1 Like

@simonbuchan : good call. Between your suggestion and @Michael-F-Bryan 's warnings, I've decided to go the IPC route.

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.