Repository: rust
Issue: Allow export of Rust symbols from a C shared object via a staticlib
Labels: A-linkage,T-compiler

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 a `staticlib` 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 from `libbase.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. https://github.com/rust-lang/rust/issues/54135#issuecomment-607935953 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 consider `libbase.so` to be a valid crate equivalent to a `dylib`. (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 a `dylib` 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

https://github.com/rust-lang/rust/issues/54135 requests something similar, but using `#[used]` on static variables - so I think it’s not quite the same thing.
https://github.com/dtolnay/cxx/issues/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)