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++?
- 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. - 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.
- 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!”.