Embeddig C code into Rust crate


#1

Hi folks

I have a crate that adds bindings for a large C library. Rust bindings to shared library are easy to use. But there are some issues with current solution:

  • Binding assumed to link application to a shared library installed at the target computer’s LD_PATH. What if target computer does not have shared libraries support (e.g. an embedded device or custom OS).
  • Compiled application is not hermetic. i.e. it requires that users installed additional system packages. Every operation system has its own way to install shared libraries and it complicats installation. What if OS does not have a suitable package? What is user does not have “install additional package” permissions?
  • It is hard to control for a user what version of system library is installed. What if a user needs libFoo.2.6 when system provides libFoo.1.2 only?
  • Keeping code in a shared library prevents to perform Link-Time-Optimization over whole project.

It would be interesting to see a way to embed a C library into Rust crate and share as one hermetic thing without external dependencies. The C code will be part of the Rust crate and distribute as a whole thing. Then cargo/rustc compile both C and rust into bytecode and process it together.

Is there anything like this possible today?


#2

I think this question would be better suited to https://users.rust-lang.org/, unless you’re interested in compiler changes that would make this use case easier?

It sounds like what you want are static libraries/static linkage. On linux musl can be used to create a completely static binary, as described in the [old] book. On other platforms you still need to dynamically link to system libraries, but you can still statically link libraries as described in the ffi section of the book. I believe that LTO works when linking Rust and C code, although you have to build the C code with Clang because the LTO object files are really just LLVM bitcode.


#3

It is possible. You can build the C library as static libfoo.a and link it with Rust.

You could include the C library in your crate and build it yourself (or with the gcc crate), you could try to find suitable one on the system with pkg-config (you can ask it for static linking). If the versions are very different, but you still want to support all of them, you can run bindgen in your build.rs.


#4

At the moment Rust emits fully compiled object files and libraries for the linker to link. External libraries are simply passed to the linker (or bundled into an rlib and passed that way) but never does Rust do LTO with external libraries. There is no LTO between Rust and C at the moment, but maybe in the future there could be.


#5

Thank you for your answers folks.

I wonder if cargo/Rust can do something better here. It would be nice to see a simpler integration, for example Cargo.toml specifies embedded C files and compile flags and Cargo takes care of compilation under the hood. It will probably need external clang that produces LLVM IR consumed by Rust.


#6

I don’t think so. Cargo used to support simpler integration (you could launch make from Cargo.toml) and that has been removed.

Just adding C files doesn’t help you use them from Rust. You need to convert the header files, and even then you end up with a bunch of unsafe functions that aren’t as convenient as native Rust ones.

So the current approach is to make bindings as a *-sys crate that enables easy linking with the C library, and then create a higher-level safe Rust wrapper for it.