Linking issues when designing a dynamic plugin-based architecture
⚓ Rust 📅 2025-11-19 👤 surdeus 👁️ 10I'm struggling to understand the subtleties of Rust library linking with respect to loading dynamic plugin libraries completely at runtime, particularly where I'd like the host library to expose some types that plugins can use.
If I wanted to do this in C/C++, I'd compile a host library to a .dll or .so, where the host library was responsible for loading plugin libraries at runtime. In order to create a plugin, I'd import just the host headers into my plugin library's project when I build it. Generally I wouldn't require the plugin library to know anything about the functions to call on the host - if it needed to do so, I'd pass a struct to the plugin containing callback functions that comprised an API, and this struct would be described in the host's public headers.
The plugin library would expose functions of a known signature (eg. to report the interface version it uses, and provide one or more entry points for the host to call), and the host would look up these symbols at runtime to make use of the plugin.
Using this model, the plugin library does not need to link against the host object code, either statically or dynamically. It just responds to calls that the host makes of it, potentially calling back into the host via the callbacks, and may make use of any types provided in the host headers.
I'm trying to work out how to implement something like this in Rust. Originally I tried specifying the host library as ["rlib", "dylib"] (so that other Rust code can use and call into it), and the plugin library as ["cdylib"]. Here the plugin exposes its functions as extern "C", and has a Cargo dependency on the host in order to access the public host types. The functions are extern "C" not specifically for C compatibility, but in order to conform to a stable ABI. They would only be expected to be called by Rust code.
Unfortunately, it seems this configuration causes the plugin library to link statically against the host. For example, when I look at the compiled plugin DLL in the Dependencies utility, any extern "C" functions that I've defined on the host end up being defined on the plugin library too. This is obviously not what I want.
My next step was to try dropping rlib from the host and just compiling with ["dylib"]. This removed the erroneous static linking from the plugin, but I got an error when trying to use the host (example from Windows):
The code execution cannot proceed because std-5740e47ddabbb05c.dll was not found.
Reinstalling the program may fix this problem.
Presumably the host library now cannot find the Rust standard library, and has not linked it in statically. No std-*.dll is present in my output directory either. I can understand wanting to avoid linking different versions of the standard library into different targets, but here it shouldn't matter as far as I understand, since the host (and any launcher binary) are compiled at the same time, and the plugin library is completely external and should be able to statically link whatever it wants.
What would be the best way to produce this kind of plugin architecture in Rust? Essentially what I'm looking for is a way to produce an external cdylib that uses "just the headers" (in C/C++ terms) of the host library, without depending on any of its implementation. I understand that running cbindgen is one option here, but given I'm planning only to write plugin libraries in Rust and not actually in C or C++, generating headers just to parse them back again with bindgen seems quite redundant.
2 posts - 2 participants
🏷️ Rust_feed