Description
clang offers the ability to mark a symbol as exported or non-exported from a dynamic shared object (DSO) using __attribute__((visibility ("default")))
and __attribute__ ((visibility ("hidden")))
. This issue requests the same in Rust, though it may be a bit more complex in the Rust case.
Another way of looking at it is to cleave the dual purpose of #[no_mangle]
where it both sets visibility and alters the exported name.
The rest of this issue explains why this would be useful.
I am working in a pre-existing complex C++ codebase. It produces multiple DSOs, let’s say:
libbase.so
libservices.so
We want to build Rust code into each of those DSOs. Internally, the C++ and Rust code is intermixed freely (for example, the C++ JSON APIs within libbase.so
call into a serde-based parser, which calls back into C++ to instantiate objects, etc.) There is no possibility of splitting these DSOs into separate DSOs for Rust and C++ code.
We build Rust code into these DSOs in the approved way, which is to aggregate a bunch of Rust libraries (rlibs) into a separate Rust staticlib
for each of the DSOs. (For example, libbase_rust_deps.a
and libservices_rust_deps.a
). The final C++ linker links exactly one of these staticlibs together with the C++ .a and .o in the final construction of the DSO. That should be fine, since the whole point of a staticlib
Rust target is to contain the Rust code and all its dependencies.
libservices.so
depends on libbase.so
. In C++, libservices.so
uses symbols exposed from libbase.so
. For example, there may be a CPP::Log
function used by C++ code within both libbase.so
and libservices.so
. That’ll be exported from libbase.so
using an __attribute__((visibility ("default")))
annotation in the source code for CPP::Log
.
So far so good.
But now we want to put a Rust wrapper around that CPP::Log
function, to make, say, rs_log
. Ideally, there would be just a single implementation of rs_log
in libbase.so
. We want to be able to call that Rust wrapper from within the Rust parts of libbase.so
or libservices.so
.
This seems to be impossible.
Our options are:
- Build a
dylib
using rustc, which exposes all public Rust APIs from the DSO. We can’t do that for the reasons cited above; we need to build astaticlib
as the Rust code is just a small component of existing DSOs. Rustc can’t be in control of the final linking. - Mark
rs_log
as#[no_mangle]
which successfully exports it fromlibbase.so
, but using a C ABI which prevents Rust discovering and using this symbol even if downstream crates are built using--extern rust_base=./libbase.so
(this latter case gives
error[E0463]: can't find crate for `rust_base`
--> ./rust_static_libs/libservices_rust_deps/mod.rs:20:1
|
20 | extern crate rust_base;
| ^^^^^^^^^^^^^^^^^^^^^^^ can't find crate
)
Solution?
It’s a problem that #[no_mangle]
has twin effects: specify a particular name for C linkage purposes, and mark the symbol for DSO export. We want to do the latter but not the former. #54135 (comment) wants to do the former but not the latter. Can we separate those?
For our needs, ideally we’d mark rs_log
with something like an #[dso_export]
annotation. This would:
- Mark that symbol with the equivalent of
__attribute__((visibility ("default")))
, such that as it moves through the rlib, then staticlib, then the DSO, it is successfully exported from the DSO. - But unlike
#[no_mangle]
it wouldn’t change the symbol name and it would remain callable by Rust. - (The likely trickier bit) When building the final
staticlib
, determine if there are any such exported Rust APIs. If so, include whatever information is required by Rust to considerlibbase.so
to be a valid crate equivalent to adylib
. (I don’t know how Rust metadata is structured; whether this is just an extra few exported symbols or if there are entire linker sections within adylib
that would somehow need to be replicated. I strongly suspect the latter so I don’t even know if this is possible without changing the final C++ linker command).
Related work
#54135 requests something similar, but using #[used]
on static variables - so I think it’s not quite the same thing.
dtolnay/cxx#219 proposes the workaround which we might need to do meanwhile (but isn’t ideal because, in the above example, RS::Log
would be duplicated between two DSOs)