24 days of Rust - calling Rust from other languages

Important note: this article is outdated! Go to http://zsiciarz.github.io/24daysofrust/ for a recent version of all of 24 days of Rust articles. The blogpost here is kept as it is for historical reasons.

In this penultimate episode of the 24 days of Rust article series we will focus on using Rust code from other languages. Since Rust libraries can expose a C API and calling conventions, using them isn't very different from using regular C libraries. A lot of programming languages have some kind of an FFI mechanism, allowing them to use libraries written in other language(s). Let's see a few examples!

Our Rust library

As an example we're going to build a small Rust library called stringtools which exposes just one function: count_substrings. Let's start with a manifest - here's our Cargo.toml:

[package]
name = "stringtools"
version = "0.0.1"
authors = ["Zbigniew Siciarz <zbigniew@siciarz.net>"]

[lib]
name = "stringtools"
crate-type = ["dylib"]

The crate type is explicitly declared as a dynamic library. And now the actual implementation:

extern crate libc;

use std::c_str::CString;
use libc::c_char;

#[no_mangle]
pub extern "C" fn count_substrings(value: *const c_char, substr: *const c_char) -> i32 {
    let c_value = unsafe { CString::new(value, false) };
    let c_substr = unsafe { CString::new(substr, false) };
    match c_value.as_str() {
        Some(value) => match c_substr.as_str() {
            Some(substr) => value.match_indices(substr).count() as i32,
            None => -1,
        },
        None => -1,
    }
}

This doesn't look as nice as your regular Rust code, but most of it is just wrapping/unwrapping values to be consistent with a C ABI. The #[no_mangle] attribute keep our functions names plain and simple, while pub extern "C" exports the function to the outside world with the C calling convention (cdecl). See the FFI guide or my blogpost about wrapping a C library for more information.

Aside: there was a funny bug regarding bananas in the algorithm Rust uses for string matching.

To build the library, run cargo build to obtain a .so file and you're good to go - time to move on to other languages.

Python

This bit was the easiest to write :-) With a little help from the ctypes module our Python example consists of just two lines of code (excluding import):

import ctypes

stringtools = ctypes.CDLL("../target/libstringtools-261cf0fc14ce408c.so")
print(stringtools.count_substrings(b"banana", b"na"))

Note that Python byte strings are mapped by ctypes to native C strings underneath.

We can run it:

$ python3 main.py
2

C

We have to redeclare the function signature and that's it. Since we exposed from Rust a C API (and ABI), from this perspective it isn't really a foreign call.

#include <stdint.h>
#include <stdio.h>

int32_t count_substrings(const char* value, const char* substr);

int main() {
    printf("%d\n", count_substrings("banana", "na"));
    return 0;
}

Compile and run (we need to link with the library at runtime, hence the LD_LIBRARY_PATH prefix):

$ gcc main.c -L ../target -lstringtools-261cf0fc14ce408c -o main
$ LD_LIBRARY_PATH=../target ./main
2

Haskell

The FFI introduction may not be as great as the Rust guide, but after reading through it and some careful googling I've managed to put together the example below:

{-# LANGUAGE ForeignFunctionInterface #-}

import Foreign.C.String (CString, newCString)

foreign import ccall "count_substrings"
    count_substrings :: CString -> CString -> IO Int

main :: IO ()
main = do
    value <- newCString "banana"
    substr <- newCString "na"
    count_substrings value substr >>= print

Haskell strings need to be converted to CString values before passing them to the foreign function. Apart from that, the code is straightforward.

Compile, link and run in the same manner as C:

$ ghc --make main.hs -L../target -lstringtools-261cf0fc14ce408c -lpthread -o main
$ LD_LIBRARY_PATH=../target ./main
2

Node.js

This was as straightforward as with Python, thanks to the ffi npm package.

var ffi = require('ffi');

var stringtools = ffi.Library('../target/libstringtools-261cf0fc14ce408c.so', {
    'count_substrings': ['int', ['string', 'string']]
});

console.log(stringtools.count_substrings("banana", "na"));

The first argument of the Library function is the location of our dynamic library. The object passed in the second argument defines a list of functions exported by the library, along with their signatures (in the form [return type, [arguments]]).

$ node main.js
2

The code for today is in a separate GitHub repository - rust-ffi-stringtools. On a personal note - I should probably add a Makefile...

See also


Photo by Okko Pyykkö and shared under the Creative Commons Attribution 2.0 Generic License. See https://www.flickr.com/photos/data_op/3173942297