Skip to content

Latest commit

 

History

History
142 lines (114 loc) · 5.78 KB

rust-ffi.md

File metadata and controls

142 lines (114 loc) · 5.78 KB

I've been spending a lot of time playing with programming languages and thinking about how I might use them in practical situations.

Rust's speed and safety make it an excellent candidate for an embedded language. By calling into Rust for expensive operations, you can help keep your host platform responsive.

Creating A Rust Dynamic Library

So, let's jump right in to it with our new, revolutionary, Rust library.
Start by starting a new Rust library with cargo:

$ cargo new mylib && cd mylib

Replace the contents of your Cargo.toml with:

# Cargo.toml
[package]
name = "mylib"
version = "0.1.0"
authors = ["First Last <[email protected]>"]

[dependencies]
libc = "0.1"

[lib]
name = "mylib"
crate_type = ["dylib"]

And replace the contents of src/lib.rs with:

// src/lib.rs
#![crate_type = "dylib"]

extern crate libc;
use std::ffi::CStr;

#[no_mangle]
pub extern "C" fn hello(name: *const libc::c_char) {
    let buf_name = unsafe { CStr::from_ptr(name).to_bytes() };
    let str_name = String::from_utf8(buf_name.to_vec()).unwrap();
    println!("Hello {}!", str_name);
}

We can now build with:

$ cargo build

And out comes The Miracle: our dynamic library, target/debug/libmylib.so.

Let's go through everything that's happening in Cargo.toml and src/lib.rs:

Cargo.toml

First we have basic information about this crate:

[package]
name = "mylib"
version = "0.1.0"
authors = ["First Last <[email protected]>"]

The libc pacakge is currently flagged as unstable, but we can still use it through the cargo library.
Luckily, the libc crate is available in lue of a bundled module:

[dependencies]
libc = "0.1"

Later, when we build, cargo will pull in our dependencies and build our library for us.

We also want to tell cargo we are making a dynamic library, and want to call it with the ever-unique "mylib":

[lib]
name = "mylib"
crate_type = ["dylib"]

Rust has multiple types of libraries it can build: rlib/lib (default), dylib, and staticlib.
If we want to dynamically load the library in an interpreted langague, we want to build with dylib.

src/lib.rs

extern crate libc;
use std::ffi::CStr;

Here is where we are importing some useful external libraries: libc and ffi.

  • libc helps us abstract platform-dependent C types to useful Rust types. For example, on my platform c_char is a type definition for i8.
  • ffi helps with the actual conversion between C types and Rust types.
#[no_mangle]
pub extern "C" fn hello(name: *const libc::c_char) { ... }

This is our C-compatible function declaration:

  • #[no_mangle] ensures the exported function signature won't be mangled by the Rust compiler.
  • extern "C" specifies the ABI we are exporting for.
  • name: *const libc::c_char a type signature for a CString
let buf = unsafe { CStr::from_ptr(name).to_bytes() };
let str_name = String::from_utf8(buf.to_vec()).unwrap();
println!("Hello {}!", str_name);

Here we get into some unsafe code so we can extract bytes out of the CString's raw pointer. We then convert the bytes into a String, and print out our very important message.

Calling Rust From Node

I spend most of my time working in JavaScript/nodejs, so let's go over how we can call in to our Magnificent Rust Library.

// mylib.js
var FFI = require('ffi');

var lib = FFI.Library('./libmylib', {
  'hello': [ 'void', [ 'string' ] ]
});

lib.hello('Rust');

The JavaScript code is fairly straightforward, with the help of node-ffi. We just need to tell it where our dynamic library is and stub out what the C ABI function signature look like. The signature uses the C types from ref, which is used internally in node-ffi.

Generator Rust-FFI

To ease the process of creating Node -> Rust libraries, I created a yeoman generator, Rust FFI.

To use, simply install yeoman and generator-rust-ffi through NPM:

$ npm install -g yeoman generator-rust-ffi

And run the generator:

$ yo rust-ffi

The idea of this project is to have Rust libraries with bindings into multiple languages. These bindings should follow good practices in the module/package structures and testing strategies, specific to whatever language the binding is for. The same repository should also be a valid package for the binding language's native package manager.

I've implemented this boilerplate and example bindings for JavaScript/nodejs in Rust FFI. All JavaScript library and testing code is stored in the js folder and it is a valid NPM module, which will compile the Rust dylib on install.

The decision to use Yeoman was simply because I haven't seen a better tool for bootstrapping project structures.

This post and the generator was inspired by Zbigniew Siciarz's blog post on calling Rust from other languages.

If you have any questions/suggestions feel free to comment, open an issue, or make a pull request.

Update 2015-05-28
Thank you to both JavaScript Jabber and DailyJS for the mentions.
I've updated the article to work with Rust 1.0 and to use cargo. If you want to see the old (broken) guide, I make the source to my site available on github.