FFI and "static" functions in headers


#1

Hi,

I’m writing bindings for libsolv library and I came up with problem of static functions declared in headers, for example:

solv/queue.h contains following snippet:

static inline Id
queue_pop(Queue *q)
{
  if (!q->count)
    return 0;
  q->left++;
  return q->elements[--q->count];
}

I’ve tried to declare it as

extern "C" {
    pub fn queue_pop(q: *mut Queue) -> Id;
}

However, I get error when trying to use my libsolv-sys crate

          /home/brain/libsolv-rs/target/debug/deps/systest-3df04080070ee608.0.o: In function `systest::fn_queue_pop':
          /home/brain/libsolv-rs/systest/src/main.rs:9: undefined reference to `queue_pop'

Which is obvious, because this function is not exported by library, but declared in header. How should I deal with such kind of things?


#2

The best approach is probably to compile your own .c file which wraps the inline function in a regular exported function, and link against that.

Alternatively, you could reimplement the function in Rust…


#3

I’ve also found this suggestion on the internet, but I’m sure how to properly integrate that with build.rs… Could you show how to do it?


#4

You can use the gcc crate to compile a C file at build time.


#5

So I ended up with doing:

  • static-functions.c
#include <solv/queue.h>
static inline void queue_empty_real(Queue *q) { queue_empty(q); }
  • build.rs
fn main() {
    gcc::compile_library("libsolv-static-functions.a", &["static-functions.c"]);
}
  • src/lib.rs
extern "C" {
    fn queue_empty_real(q: *mut Queue);
}
pub fn queue_empty(q: *mut Queue) {
    unsafe { queue_empty_real(q) }
}

It doesn’t seem pretty (duplication of same information 2 times), but it compiles. Is it something what you meant?


#6

Yes, that’s it. It is a bit of hassle, but this approach works with arbitrary C functions.

As @comex said, the alternative is to reimplement the function in Rust manually. That would simplify the build and give you inlining the original function was intended for.


#7

Having functions defined in headers is common enough that it seems like the ffi interface should be extended to support it explicitly or at least provide tooling to make handling it easier.


#8

Having language support for that is questionable (as it would make FFI geared towards C and not the ABI). But it seems like a good addition to things like bindgen.


#9

I had forgotten about bindgen, that seems perfectly fine. But I did say tooling :wink:


#10

When I encounter such inline functions in Windows API, I just reimplement them in Rust in winapi.


#11

https://github.com/servo/rust-bindgen/issues/399 this seems to be your relevant issue, btw. It seems they are currently ignored.


#12

@ignatenkobrain @kornel I’ve been using the rust-c crate, a fork I did of rust-cpp (forked because my build environment did not have C++ support), to let me manage the C code as part of my Rust code. This avoids the duplication.