Creating bindings to C APIs

In the world of GObject API there are XML files describing the API and so you create two crates one with the FFI stuff and one with the Rust-facing stuff. Clearly all very sensible as that is the way to tools lead you.

For other C APIs I am assuming bindgen is the de facto standard tool for generating the FFI. The question is should this be a standalone crate with any Rust-facing additions in a separate crate. Alternatively, should the FFI bits and the Rust-facing bit all be in the same crate?

I recommend separating exposing the C interface and linking concerns from higher level Rust wrappers.

https://kornel.ski/rust-sys-crate

1 Like

Thanks, I shall follow that lead.

One issue I am stumbling on at the minute: the C API I am binding/wrapping has a "function" that is a macro. Bindgen appears not to generate anything for this. This implies manually providing an implementation of the function. I am guessing the *-sys crate is the right place for it, rather than the * (rust-y) companion crate. Is there an example of doing this sort of thing that I can take a look at?

Is there a way of getting Bindgen to output a file with newlines, having the entire output as a single line file is difficult for finding things out about the generated file?

If you have rustfmt installed, bindgen will use it.

Yes, the sys crate is the right place to provide a macro replacement.

I usually put my replacement code in lib.rs, and bingen-generated file in ffi.rs which is pub use ffi::* in the lib.rs

Apparently I have rustfmt installed via rustup but the bindgen generated file isn't using it. :frowning:

Does this imply not running bindgen as part of a build but instead generating and committing the result of bindgen to a file in the repository of the *-sys package?

If the C library has a stable ABI (i.e. there's generally one version of it and it doesn't change often in incompatible ways), I recommend committing the generated file in your sys crate. That saves users from having to run bindgen, which is quite a heavy dependency.

1 Like

Works for me. :slight_smile:

Now to find out how to organise things so that the bindgen is a manual thing not a run every time thing. Also committing the generated source further pushes me towards finding out why rustup is installing a rustfmt but not one that actually works.

For bindgen generation, a shell script in the repo works for me.

For some projects, like libjpeg, I've just ran it once and won't run it ever again, since I'm aiming for ABI settled 20 years ago :slight_smile:

So not the Cargo.toml/build.rs driven system as for the bzip2 example and real crates. I'll have to see how stable the API is and take a view. I'll hunt out the libjpeg example. Thanks for the feedback, very much appreciated.

I found out why the bindgen wasn't being formatted, rustfmt wasn't installed properly – I have no idea how or why. Now reinstalled properly and working as you predicted. Life is a lot easier now. :slight_smile:

@kornel As a supplementary question: bindgen does nothing with C macros that have parameters. If some of then should be offered as (potentially inline) functions should they be in the lib-sys crate or in the lib crate?

It's fine to put macro replacements in the sys crate. There isn't a hard rule about this, since macros don't affect linking or ABI in general.

Keep in mind that often C uses macros only because it's bad at inlining functions or lacks generics, so a replacement for a C macro isn't always a macro. It might be a (generic) Rust function.

I was thinking Rust functions replacing C "function" macros and letting the Rust compiler handle inlining.

I shall add the crucial ones manually in the lib-sys crate.

Thanks for the prompt response, very much appreciated. It means I can get moving on this whilst the energy is high. :slight_smile:

@kornel Of course now is the decision what to do about the ioctl calls. The nix crate allows for creating Rust-facing ioctl functions whereas the C just has the ioctl number macros. The nix generated ioctl functions should perhaps should be in a lib crate, but I think I might do a doublethink and put them in the lib-sys crate since there is unlikely to be a lib crate for this FFI binding.