Using Rust outside Rust
Say you've got a bunch of really fast Rust code (remember, Rust is about the same as C, sometimes faster) and want to call it from a slower language like Python or Ruby, it's actually pretty easy to do. If you can run C code, you can also run Rust.
Quick Links
For those too lazy to scroll:
Original Function
We're going to make a contrived example to check if a number is prime.
$ cat prime.rs
#![feature(inclusive_range_syntax)]
#![feature(step_by)]
pub fn is_prime(n: u64) -> bool {
match n {
0 | 1 => false,
2 | 3 => true,
n if n & 1 == 0 => false, // even numbers
n => {
let limit = (n as f64).sqrt() as u64 + 1;
for i in (4...limit).step_by(2) {
if n % i == 0 {
return false;
}
}
return true;
}
}
}
You need to explicitly tell the rust compiler not to mangle the function name, and export it as a C function. So the function signature needs to be changed a bit...
#[no_mangle]
pub extern "C" fn is_prime(n: u64) -> bool {
...
}
And to make sure our library is callable from other code, it needs to be
compiled as a cdylib
(with optimisations, of course).
$ rustc prime.rs -O --crate-type cdylib
$ ls
prime.rs libprime.so
Aaaannnnddd we're done. How easy was that?
Because the library hasn't been installed into one of the standard locations
(i.e. /usr/lib/
), you'll also need to add an environment variable so things
know where to find it. You'd need to do the exact same with a C/C++ library
as well.
$ export LD_LIBRARY_PATH=.
C
Considering almost everything is descended from C and can interoperate with C, it makes sense to check out C first. In this case, calling Rust code from C is just like calling code from another library. Even easier if you take into account that you don't even need to write a header file.
#include <stdio.h>
extern int is_prime(unsigned int n);
int main() {
unsigned int n = 1234567;
printf("%d %s prime\n", n, is_prime(n) ? "is" : "is not");
return 0;
}
And you link in the Rust library just like any other compiled DLL
or *.so
file:
$ gcc main.c -L. -lprime
Note: because of a compiler bug at the time and the std library isn't being used directly, it isn't linked in when
rustc
creates our shared object. Unfortunately, a couple things were indirectly needed from the std library so I was getting linker errors later on. The fix was to add a dummy function which uses something from the std library (e.g. a print statement).
Lua
Next is calling our Rust
code from Lua
. Lua
is a super light-weight
language (about 10,000 lines of C) often used for embedding into applications
to make them scriptable. It's also one of the fastest scripting languages
in use at the moment.
Here's the script I'm using:
ffi = require("ffi")
ffi.cdef[[
int is_prime(unsigned int n);
]]
rust_lib = ffi.load("./libprime.so")
n = 1234567
if rust_lib.is_prime(n) then
print(n, "is prime")
else
print(n, "is not prime")
end
$ luajit main.lua
1234567 is prime
That was easy enough, all you need to do is load()
the *.so
file and use
it like pretty much any other library in Lua
.
Python
Next up is Python
, unlike Lua, Python is well known for being slow.
Particularly when it comes to computationally expensive programs.
Python's standard library comes with a useful module for working with C (or
any other compiled language) called ctypes
. You can use this pretty much the
same way as in Lua by creating some sort of object out of the loaded *.so
file and then calling it like normal.
import ctypes
primes = ctypes.CDLL('./libprime.so')
n = 1234567
if primes.is_prime(n):
print(n, "is prime")
else:
print(n, "is not a prime")
Python also has a really nice library called cffi
. With ctypes
you usually
need to define each function, its input arguments, and what it returns in order
to have type safety, but cffi
uses a completely different approach. You just
pass it the text for a typical C header file and it does the rest. For larger
projects, or things which may still be evolving, cffi
is a much nicer library
to use.
from cffi import FFI
ffi = FFI()
# Load a header entry for the Rust function
ffi.cdef("bool is_prime(int n);")
lib = ffi.dlopen("./libprime.so")
n = 1234567
if lib.is_prime(n):
print(n, "is prime")
else:
print(n, "is not a prime")
# trying to use an invalid datatype should fail
lib.is_prime("foo")
Golang
Another language I've been interested in lately is Go. It's a compiled, statically typed, gabage collected language developed by the guys at Google and primarily oriented towards networks, concurrency, and microservices.
Go has a nice trick for allowing you to interact with C code. It's got special
behaviour when you do import "C"
. Any comments above are passed to the
underlying compiler, meaning you can # include
any C library just like you
would in native C. Then, using reflection and other forms of black magic, you're
able to call anything in the imported namespace as an element of the C
object.
i.e. calling printf()
is just a case of doing C.printf(...)
.
package main
/*
#cgo LDFLAGS: -L. -lprime
#include "./prime.h"
*/
import "C"
import "fmt"
func main() {
var n C.uint = 1234567
if C.is_prime(n) != 0 {
fmt.Printf("%d is prime\n", n)
} else {
fmt.Printf("%d is not prime\n", n)
}
}