I'm rewriting a C library bit-by-bit in Rust. I want to replace one of C functions with Rust implementation. I want to call Rust code from C, but not via pointer callback, but by a regular call via a global linked symbol.
I'm using the gcc-rs crate to build the C code as a static library, but I don't know how to link Rust into that static library.
My project layout is:
top-level Rust wrapper, with build.rs that builds:
a static C library that calls an extern C function
which I want to implement in Rust
I've tried two approaches:
#[no_mangle] pub extern "C" fn foo() in the top-level project. This doesn't work, because the C static library fails to link due to missing _foo symbol.
Splitting Rust crate into two, with the second sub-crate being a static library. However, the sub-crate builds as deps/libfoo-4299fca20d9ad449.a, so I'm unable to guess the hashed filename in my build.rs, so I don't know how to link it with the C code.
What's the solution for calling Rust code from C within static libraries?
To clarify, I have a problem structured like this (but with lots of code in between, and many more functions involved):
The problem is, that apart from passing down function pointer as an argument (which is too cumbersome to do for all my functions) I don't know how to make rust_implementation() available and linking properly in c_function().
I recently developed https://github.com/chrisliebert/rust-genbind which will generate C headers from #[no_mangle] Rust functions in a dynamic or static library. This was an effort to automate the work involved in exposing this Rust API to C. There is still some work that would need to be done to make this tool complete, such as the ability to specify cfg attributes and features, and to support passing in boxed and mutable pointers to #repr( c ) marked structures.
I had better luck using cmake-rs to build the libraries since there are more configuration options available in CMake. Maybe this example project will be helpful to you.
I'm using Cargo for all Rust code (and I'd like to keep it that way, since I have Cargo dependencies all over the place).
I'm using gcc-rs to compile the C code from build.rs as a static library and link that into its parent crate. That part works as far as calling Rust->C is concerned. It's doesn't seem to be enough to get C->Rust calling though.
I don't know where to plug C->Rust linkage. I suppose I should have a Rust crate of type staticlib as a build-time dependency of a regular Rust crate, but I don't know how to find it in build.rs, since Rust/Cargo produces staticlib with hashed filename.
While I can't say for sure what's going wrong here, one of my projects does exactly the same thing, so I can tell you that it's possible!
In my project there are two crates: A and B, where A is a normal library crate (i.e. not specifically a static library crate), and B is an executable crate that depends on A. A has a build script that uses gcc-rs to compile some C++ code. The crate exposes this C++ code like a standard -sys crate, but also exposes some #[no_mangle] pub unsafe extern "C" fns which are called by the C++ code. B calls into the C++ code.
Carol (of rust-book fame) translated the zopfli compression algorithm from C to Rust, function by function, so exactly what you are doing.
Her entire process is online here on github. The commits and project layout are probably very useful to look at. She also have a presentation on the process, detailing the steps.
I've tried the most basic solution again and got that working on a simple project, so I'll have to find where my bigger project goes wrong. For the record a simple solution works:
I have been developing a framework called genbind which can be used to automatically generate the C header file declarations. Please let me know if you find any bugs or want to contribute a pull request.
That is interesting, it is a useful task to automate. genbind does not (yet) support manifest configuration, the rust type keyword, or #[cfg] conditional compilation attributes. I didn't find cbindgen or moz-cheddar when I initially searched for a tool to perform the task. genbind is less feature-rich than both frameworks and like moz-cheddar genbind is licensed MIT, and it can be forked and trademarked unlike cbindgen (MPL). The lack of features makes genbind's source smaller and simpler (for me) to understand than the moz-cheddar source. I enjoyed learning how to use the syntex_syntax library to develop genbind and hope the functionality someday makes it into the Rust compiler or Cargo build system.