FFI etiquette / best practices?


#1

Hi all, I have a few simple questions that essentially boil down to FFI etiquette / best practices:

I wrote a -sys crate for the SUNDIALS library. I know that Rust doesn’t currently draw a large numerics community, but I figured a SciPy-like-wrapper over some mature nonlinear algebraic and ODE solvers could go a long way to help. My questions are related to this activity:

  1. SUNDIALS is BSD-3-Clause licensed. I don’t believe I need to license my -sys crate identically (I would prefer a 2-Clause or an MIT), but I feel like I should “advertise” the underlying 3-clause license – making my -sys crate the same propagates that visibly to crates.io. Is this a concern I should be having?

  2. My -sys crate uses the SUNDIALS git repo as a submodule and builds the whole thing from source at cargo build-time – thus I depend on cmake (to build) and bindgen (to generate FFI on the fly, in case build options are changed by features or weird platform defaults). This means any user of the crate needs to have CMake and Clang already installed on their system – is this asking too much? Everything I learned about Rust FFI came from an excellent tutorial, but it did not give much advice on pre-building across multiple platforms/configs and bundling the bindgen output (in particular, were I go to this route, what platforms/configs I should target?).

  3. As mentioned, I am going to write an ergonomic wrapper over the sundials-sys crate. Is it generally acceptable to coop the SUNDIALS name and call my project rusundials, or sundialrs, or something else similarly inspired? Or is it better to not imply any affiliation and go for something entirely unrelated? After all, my wrapper will express API opinions very different from the authors of SUNDIALS. (Or does it not matter at all?)

Finally, not really a question, but if anybody has strong opinions on what a good ODE/root-finding wrapper should look like in Rust, I’d be very interested in hearing your thoughts. For something quick and simple I was going to go the SciPy route, but we have lots of stuff Python doesn’t – maybe another API else makes more sense!


#2
  1. If you bundle any code from the library, you have to have the same license (you may require another license for your sys part, but that’s in addition to the original one). Either way it’s easiest for users if you use the same license, so they don’t have to add more licensing documents to their project.

  2. cmake is not too bad, but mildly annoying if you don’t have it already and need to install it. Bindgen is a bit heavy due to requiring LLVM, and slower to compile than pregenerated bindings. If the library has a single, stable ABI, then pre-generate. If you don’t know what library version you’ll get, then you may need to use bindgen at compile time.

  3. Currently crates.io doesn’t have any policies against name squatting, so grab names while you can, or someone else will. Personally I think using project’s name is fine if the project isn’t planning to have their own official Rust support, and you’re going to provide the best crate for it.


#3

Thanks for the advice, and extra thanks for writing that FFI tutorial! With respect to #2, SUNDIALS is distributed in such a way that the user is expected to select from a number of libraries and parallel libraries at compile-time, e.g. turn on OpenMP vectors, but turn off pthreads vectors, or turn on LAPACK solvers and turn off SuperLU solver, and finally build CVODE but not CVODES, IDA but not IDAS, etc. I have most of these setup as cargo features, with reasonable defaults. If I was to pregenerate the bindings, do I turn everything on, and just have the resulting generated Rust code calling unimplemented functions? (Would that work?)


#4

When you have extern "C" { fn foo() }, and the C library doesn’t have foo(), but you never actually call foo(), it happens to work fine, at least on x86-64. But I’m not sure if that’s guaranteed to work.

If you can edit the bindings and add #[cfg(feature = "foo")] to functions that would be unavailable, that will be the safest. The build script is allowed to set cargo features, so you may be able to auto-detect what’s supported.


#5

Ah ha, you’re absolutely right – obviously I should just cfg-annotate the full bindings output. Thanks again for your help!