Skip to content

Allow export of Rust symbols from a C shared object via a staticlib #73295

Open
@adetaylor

Description

@adetaylor

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. #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 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

#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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-linkageArea: linking into static, shared libraries and binariesT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions