First off bear with me I'm a hobbyist C++ guy, and I basically have zero Rust experience (yet).
I mostly build apps that are targeted to the creative industry, think 3D viewers, media players, etc... Lots of UI, GPU shaders, high performance stuff, mixed with big industry libraries like Alembic, USD, OpenColorIO etc.
Now in my area, most apps are closed source C++ and have been around for a very long time. They all offer SDKs to create plugins for them, including new UI panels etc, with a ton of flexibility. Think Houdini, Maya, Nuke.
Most of them are build on Qt, and offer a public API. One can program against that public API, reuse the Qt libraries provided by the app, and compile to a DLL that will be loaded as a plugin. It's easy, and the UI will even be themed correctly because it inherits the QApplication at runtime.
Of course in order to do so you must use the same MSVC version as the app SDK, same exact libraries etc...
Now as I'm developing a brand new app in the same style, and closed source, I would love to use something a little less messy than C++ and Rust of course sounds like a great fit. wgpu + iced is what I'm looking at. But the main issue I'm struggling to solve is what is the approach for a similar plugin development experience as mentioned above in the c++ world?
Especially, because Rust kinda wants everything to be statically linked, it seems like I would need to instruct user to get their own iced dependency pinned to a specific commit. But then I hear all this chatter about unstable ABI, and how would plugin Iced widget/views communicate back into the main host app?
It feels like I need to see things from a different angle but I need the guidance from more experienced people
I have a feeling I can't be the only one with such a use case?
The very TLDR version is all the same techniques for an FFI work in Rust, you load a library (eg with libloading) then cast the entry points to C compatible function and data types (with extern "C" and #[repr(C)] respectively) - the only real difference is you are dealing with unsafe code so you'll want to wrap up the API which has it's own set of funky logic to learn: check out the Rustinomicon, especially the FFI section, for a general guide.
That's all a bit annoying, so it's also pretty popular now to instead use something like WASM (eg with wasmer), which is pretty safe and simple in comparison and doesn't give up much performance.
Edit: for the specific example of iced, I think you could pretty much "just" provide a C-ABI version of the Widget trait, which generalizes the rendering and state APIs enough there shouldn't be any concerns there, but I don't think you will want to do that naively for two reasons:
Once you start pulling at the threads of the API it becomes a lot of work! You'd need to wrap the entire wgpu API in theory, since that's exposed though the Renderer type parameter, for example.
It ties you very specifically to the current release APIs which are quite unstable still for both wgpu and iced.
Better to find the specific subset of functionality you want to expose and share at that level: for example that there is a column widget, containing a label and a text box, and have your host pass over this tree and render to iced elements. You'll also need to figure out how you want state to get stored and updated, you could handle that mostly on the plug-in's side I think but there's trade-offs for both sides.
In short, there's no simple option that just exposes these crates as shared libraries that both sides can load and call, for several reasons: they would also all need to do all the above work to get a stable ABI, and there's no good way to do that when the API uses generics (which are very common in Rust crates), and also just that things are really simple when everything is statically linked and adding dynamic linking largely just makes things confusing.
As an aside: technically you can use shared libraries with the native Rust ABI, but it's only really for crates all compiled together as part of a workspace, so it doesn't make sense for a plugin approach.
yes, the rust abi is unstable, the usual solution is to create a customized application specific plugin api with ffi and the C abi, don't expose the underlying rust libraries to the plugin. tools like cbindgen can be used.
creating these ffi bindings with C abi can be a hassle though, especially if you have a large surface area for your plugin api, and the C abi boundary can increase the chance of memory safety bugs.
there's a library to solve the loadable module problem in rust. the repository contains several examples demonstrating the usage:
basically, all the plugin apis are defined in an interface crate, you use abi-stable to annotate them. then both the implementer and the consumer can pull the same interface crate as a dependency, while the host and plugins are themselves decoupled with each other.
disclaimer: personally I find no issues using it, but I have only used it in small toy programs, I don't know how well it scales.