Rust FFI -- Great and Timely News

The SWI-Prolog FFI started from the Quintus one that is also the basis of the SICStus one (don’t know about GNU). It consists of a Prolog declaration of the foreign functions (name, types). This was used to generate wrapper functions that are then compiled and linked with the target foreign code. This stuff is still there in library(qpforeign). The SICStus emulation comes with swipl-lfr.pl which does the same job as SICStus sp-lfr. Both interfaces also provide passing as term_t and use C functions to extract from, create and unify Prolog terms.

I decided to extend the set of interface functions, notably with convenience functions that unify directly with native types, so you can unify a term_t with an integer, an atom created from a char*, etc. I like this much better for several reasons.

  • Yes, for add_two_ints(x,y) the automatic wrapper generation is great. At the moment that the desired Prolog types that are handled get more complicated or the desirable mapping from Prolog to C or visa versa (think about atom vs. string to char* or wchar_t*) things get harder. Quite often you’ll end up with automatically generated wrappers for multiple C functions and Prolog code that switches between those. At least, that was what we found ourselves doing when programming against early versions of Quintus that didn’t yet have term_t back in the 80s.
  • If one assumes the C function is already there, i.e., a function from an existing library, the automatic wrappers are often not too bad. It is still dangerous though as the functions and there #defined constants (or enums) as well as the types may change and while the library is still source compatible, it is not binary compatible and without anyone realizing the interface is now broken.
  • In many cases the raw functionality of C libraries is not what you want to expose to the Prolog user. As you design a more high level and Prolog friendly API you just as well write this in C. In part of the above reason, in part for performance and because there is no double declaration that may get out of sync.
  • Additional generator steps complicate the build toolchain that is needed.

With C++ we can use C++ strong typing and type polymorphism to simplify the interface.

Finally I once wrote the ffi package that allows calling functions in dynamic libraries without a C compiler. To avoid getting out of sync this parses the C header files to get access to the types and constants. Unfortunately it doesn’t work great, notably not for the headers that come with the C library. Many of these headers uses all sorts of compiler specific annotations. GCC already has quite a few of these. Recent Clang versions have even more. This makes the maintenance hard (I think the package is broken on MacOS at the moment). Modern libraries and headers also tend to wrap API calls in inline functions and/or macros. This is fine for a source wrapper based approach, but hard to deal with when directly accessing the dynamic library.