So, im just curious.
For example: i have a crate my_lib with the following code in Cargo.toml:
[lib]
crate-type = ["dylib"]
Then i can use the library in other crate:
[dependencies]
my_lib = { path = "../my_lib" }
This allows me to use my_lib in the program just like any statically-linked crate. And my_lib is compiled to a separate shared object.
But can rust actually lazy load dylib crates OR at least allow to still boot an executable even when required shared object is not present? By running i mean that program will be able to check that it lacks the library and will run without some features.
You might think that manually loading symbols via dlopen (or dlopen wrappers) is the solution. But there is the problem that in that case i cannot use types and structs and traits, etc, from the crate. I can only use loaded symbols.
What solutions, i think, might exist:
Being able to pass some
RUSTFLAGS, something remotely like/DELAYLOADin windows.Being able to use types from other crates without linking those crates at all. Just manually creating wrappers for each function.
Being able to manually create some sort of rust compiler plugin, that will in the end of the compilation collect a list of all the symbols and types and generate a wrapper-crate based on this.
Some code-generating crate that generates wrappers of other crates. Those wrappers would manually load symbols from other crates, while still providing functions and types.
Do NOT care about being able to C-FFI types and ABI stuff. For me its acceptable to only use .dll/.so/.dylib-s that are compiled with the exact same version of the compiler, so ABI is the same.
For the purpose of this example, I would organize the crates like this.
lazy_appis the application that needs to optionally load a crate at runtime.lazy_interfacedefines the elements that anyone could expect from such a dynamically loaded crate: at least a trait and some utilities to obtain a dynamic object implementing this trait.lazy_impl_aandlazy_impl_bare two examples of dynamic libraries implementing such an interface.lazy_interface/src/lib.rslooks like thisLazyTraitshould expose everything that could be expected from a dynamically loaded crate; this example relies only on two trivial functions.LazyInterfaceprovides the boilerplate to obtain a dynamic object implementing this trait.I used
libc::dlopen()to keep it simple, but some other solutions might help portability.Note that this crate is a usual
"lib"crate.lazy_impl_a/src/lib.rsandlazy_impl_b/src/lib.rscould be defined like thisThe implementation of the trait does not have anything specific to dynamic code loading.
We just need to expose with
no_manglea function providing a dynamic object implementing this trait; this function must match exactly the prototype expected in theLazyInterface::makefunction pointer.Note that such crates have the
"cdylib"crate type and must be built as well as the application (the application won't build these crates because they are not part of its dependencies).Finally,
lazy_app/src/main.rscould be like thisOnly the resources of
lazy_interfaceare known, and the implementations are loaded only if they are available.Note that all of this relies on a bunch of
unsafeinvocations.It is very easy to introduce bugs.
For example, if the prototype of the
make_lazyfunction exposed in the implementation does not match exactly what is expected in theLazyInterface::makefunction pointer, anything can happen.Another terrible thing could happen if the
LazyInterfaceis dropped in the application while some resources it created (directly or indirectly) are still in use.This example is just an idea; it should be double-checked in order to detect anything that is or could become unsound.