Rust's Great Interoperability

#0: Rust on the Rocks

You can find a lot of discussions online on why Rust is great - it’s beautiful, memory safe, and faster than the Tesla Roadster Remember when Elon launched his into space? - but perhaps my favorite thing about Rust is its fantastic FFI, which allows for interoperability with almost all modern languages.

What’s the advantage of Rust for this purpose over, say, C or C++?

  1. C - An oldie and a goldie, C allows almost any language out there to bind to it. The problem is C has no real package manager (outside of maybe clib), and many programmers prefer more feature-full, quickly incremented languages these days.
  2. C++ - The LAH of programming languages, C++ is fast and efficient, if not a little violent. The problem is C++ has almost-nonexistent support for bindings.
  3. Rust - Though it’s a newbie, Rust is hella fast, beautiful, and loved by developers. It rivals C/C++ in almost every way; most importantly for this discussion, it has a C ABI, meaning it can be called from every language that can also call C.

Maybe I’m just really edgy and want to do the cool new thing, but Rust is a great option for developing safe applications that need to be efficient and easily integrable with applications written in other languages.

#1: Creating an App

Much of the work I do is done in Crystal, a compiled language with Ruby-like syntax. Crystal allows me prototype applications and write scripts very quickly, just like Ruby or Bash, while being extremely fast. There are some applications (read: APIs) that would be better written in another language (read: Rust), but that I want to interface with apps written Crystal. And so, bindings come into play For Python, Ruby, and JavaScript (Node), Rust’s FFI is cleanly outlined. For Crystal it is not, but I was able to figure it out even without any documentation, so the process I describe is probably similar for other languages you would like to integrate with Rust..

Imagine we want to create a variable greeting function,

hello(name) -> "Hello, #{name}!"

Luckily, this is only 3 lines of Rust.

But this function, as is, cannot be read by other languages when compiled. For that, the function must be extended with a C interface, which requires the libc crate.

All that must be done is to include libc, create an function definition along the C ABI, and wrap the function with C types.

Observe that a c_char pointer is used for passing strings - this is because Rust strings are different from those of other languages.

In Cargo.toml, specify the build to be a dynamic library so it can be accessed by other processes.

And now,

#2: Bind the Library

Crystal allows linking to C functions and types via a lib declaration. And because Rust supports the C ABI, we can also link Rust functions!

Suppose that hello.rs builds to target/release/hello.dylib relative to the current path. Then we can bind the library in four lines:

Note that String.new must be used to build the char pointer returned by LibHello.hello

And compile to print “Hello, world!”.