In the project i'm working on we have an ambition to grow the amount of rust in our codebase. Yeah! we develop both shared libraries and the applications that make use of these libraries.
We are at a point where we could gain from using rust interfaces for our libraries instead of linking against the native C interface. Since we still have applications using the C interface we need to support both interfaces, C and Rust until the last C/C++ application dies.
I successfully did a quick proof of concept, where i created a cdylib that exposes both a rust interface and re-exports the actual interface of the C library by wrapping them again with
extern "C" fn .. (this is probably not needed, but i just wanted to make sure im exposing both C and rust symbols).
[Q1] Is my idea of having 2 interfaces in a library flaud ??
I see 3 solutions:
- The simplest solution, create a new library next to the legacy ones. Im not liking this idea because i need to explain to my customers why they need to install twice as much libraries. it will create confusion
- Build the rust part, build the library, stitch all o. files together with
ar and link a shared library.
- Link native library as static lib into my rust project and have cargo somehow expose native symbols publicly
[Q2] Which option would be the least painful??
[Q3] Any considerations? things i overlooked?
note: Im aware that Rust ABI is not stable. Its not a concern since we ship always full releases
Are your libraries closed-source? Rust libraries contain a lot of information in their crate metadata. This includes source locations (file paths and lines and column numbers) for a lot of things, MIR of
#[inline] and generic functions, all type, function and constant definitions (including private items). To be able to link against a rust crate you need the crate metadata of the crate including all of it's dependencies. Cargo also doesn't handle pre-compiled libraries. It may be a good idea to expose a C api from your main rust crate and compile it as staticlib or cdylib and then write a thin wrapper around this C api exposing a rust api. This thin wrapper can then be provided as source code (or in compiled form).
Are your libraries closed-source?
Option 3 will be a painful endeavour, as i need to have all my linker arguments (which i use to build the native library) available and pump them into my rust shared library crate. a way to work around this is maybe to build the native library with cc in my build.rs file.
So from that perspective option 2 is the most elegant. Not sure if i can archive rust libraries into a native .a library? i guess the anwser is no if rust requires a truckload of metadata
It may be a good idea to expose a C api from your main rust crate and compile it as staticlib or cdylib and then write a thin wrapper around this C api exposing a rust api. This thin wrapper can then be provided as source code (or in compiled form).
this is what we are doing currently actually. I assume that these thin wrappers are actually used by the applications to talk to the C interface of my shared library?
Rust can produce .a files using
--crate-type staticlib, however this doesn't allow using them as rust crates due to the missing crate metadata. While you could technically link .rlib files using a C linker if you don't use liballoc or libstd, this is not officially supported and requires you to manually find all necessary .rlib files. If you bundle them together into a single archive rustc won't be able to find the crate metadata anymore as there would be multiple conflicting metadata files. You could also have rustc produce a dylib using
--crate-type dylib. This will allow linking using both a C linker and usage as a rust crate. However usage as a rust crate still requires the crate metadata for all dependencies. Both
.rmeta (crate metadata only) and
.rlib (crate metadata + object files) files satisfy this need.
Yes, that was what I meant.
Thanks bjorn i think you answered my questions perfectly. Thanks for helping out!
You may be interested in using something like
safer-ffi, which is quite a good fit for this use case:
The main thing you'll have to keep in mind is that Rust code using your lib as a crate will need for it to be of the
crate-type, but you'll also need the
cargo to compile it into a shared library.
The advantage of
safer-ffi will thus be that the Rust callers will maintain the strong typing system, while
safer-ffi will take care of exporting an unmangled version of the function for the
cdylib / raw ABI, while also making sure the types involved in the signature of such exported functions have a well-defined ABI and layout (for which it can also auto-generate the corresponding C header), thanks to the
ReprC trait encoding this information.