Day 23 - calling Rust from other languages
Relevancy: 1.9 stable
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"]
[dependencies]
libc = "~0.2"
The crate type is explicitly declared as a dynamic library. And now the actual implementation:
extern crate libc;
use std::ffi::CStr;
use std::str;
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 { CStr::from_ptr(value).to_bytes() };
let c_substr = unsafe { CStr::from_ptr(substr).to_bytes() };
match str::from_utf8(c_value) {
Ok(value) => match str::from_utf8(c_substr) {
Ok(substr) => rust_substrings(value, substr),
Err(_) => -1,
},
Err(_) => -1,
}
}
fn rust_substrings(value: &str, substr: &str) -> i32 {
value.matches(substr).count() as i32
}
The regular Rust function rust_substrings
is wrapped in some magical code 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 three lines of code (excluding import):
import ctypes
library_name = "../target/debug/libstringtools.so"
stringtools = ctypes.CDLL(library_name)
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/debug -lstringtools -o main
$ LD_LIBRARY_PATH=../target/debug ./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/debug -lstringtools -o main
$ LD_LIBRARY_PATH=../target/debug ./main
2
Node.js
This was as straightforward as with Python, thanks to the ffi npm package.
var ffi = require('ffi');
var library_name = '../target/debug/libstringtools.so';
var stringtools = ffi.Library(library_name, {
'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.