I'm trying to write a wrapper struct around a shared libary in Rust:
pub struct LibraryWrapper<'lib> {
lib: libloading::Library,
func: libloading::Symbol<'lib, unsafe extern fn() -> u32>
}
impl<'lib> LibraryWrapper<'lib> { // lifetime `'lib` defined here
pub fn new() -> libloading::Result<Self> {
let lib: libloading::Library = libloading::Library::new(LIB_PATH)?;
let func: libloading::Symbol<'lib, unsafe extern fn() -> u32> = unsafe { // type annotation requires that `lib` is borrowed for `'lib`
lib.get(FUNC_NAME)? // borrowed value does not live long enough
};
Ok(Self {
lib, func // `lib` dropped here while still borrowed
})
}
}
How do I tell the Rust compiler that func
will not outlive lib
because they're in the same struct? Thanks!
As mentioned, the borrow checked needs to ensure you don't destructure something to keep the func and drop the lib. I've wrestled with the several times and came to the following conclusion:
Dynamically linked libloading
stuff is one of the few things where I think it's acceptable to turn into a static
. If it's a hardcoded so/dll/dylib I generally make a special static with Once
for it so it takes legitimate effort to drop it. For unknown-at-compile-time loaded ones (e.g. managed via a config file) I generally Mutex-protect a static
central registry that's just a HashMap
with a key for the library name or path, and only allow the libraries to drop when the whole program terminates. Yes, this is basically a singleton, no I don't generally recommend doing this for almost anything else, this case is a special exception.
For added safety, I do recommend you isolate the static and only use functions or structs to refer to it, especially with the Hash Map registry because it's easy for someone to do a remove
on it or overwrite the HashMap, or replace a lib with the same name carelessly. Since it's static, it still allows you to store the functions as another static/singleton as well without having to query the library for them every time if you really want (but I'm not sure there's even much performance overhead just requesting it every time, haven't benchmarked it).
I do this for a few reasons:
'lib
lifetime or library function argument or additional member when other than the fact the library you're linking is technically dynamic it's just referencing a dependency. Your program ends up agnostic if it becomes, e.g., a statically linked library later (just delete the setup lines in the file wrapping the static
dynamic library and change them to functions that point elsewhere).static
is for if nothing else.unsafe
initialization and function loading code to one file (though a wrapper struct admittedly takes care of this too).The most common exception to these reasons I'd see someone having is option 4, like if you have a highly configurable plugin system where the extra memory taken up by the plugins may legitimately matter if the user wants to disable old ones and activate new ones, and in that case devising a wrapper scheme may be the right call, or alternatively adding an unsafe fn close_dynamic_lib_no_seriously_do_you_really_want_to_do_this(self)
in your library wrapper.
The only other exception I can think of is if you're writing something like a library for crates.io
where you really want the user to be able to manage their dynamic libraries and supply them to your library for you (e.g. it may be in a non-standard path or they can choose between several implementations), and even then it's still probably beneficial to modify this way of doing it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With