What follows are the slides from a talk I gave at the PDXRust meetup. This presentation was very live demo example heavy, and I’ve replaced the demos with links to specific tags in the example code’s git repositories.

What the FFI?!

What does FFI mean?

  • Foreign Function Interface

  • Wikipedia:

    A foreign function interface is a mechanism by which a program written in one programming language can call routines or make use of services written in another.

  • Using C libraries in Rust

  • Using Rust from Python or Ruby or NodeJS or …?

Why FFI?

  • Reuse…
    • …code that someone else wrote for you
    • …code you already wrote
    • …code that already works
  • Provide
    • an optimized implementation of that inner loop,
    • with fearless concurrency,
    • and memory safety,
    • without rewriting the rest of your application.

Hello, World!

Hello, World: Takeaways

  • extern "C" { ... } to declare the existence of C functions

  • FFI is always unsafe

  • The libc crate has all the C type definitions

  • Compiling and linking involves finicky bookkeeping, so leverage tooling

Leverage Tooling

  • gcc crate to invoke the C compiler on individual .c files

  • cmake crate to build larger C libraries with cmake

  • build.rs to do arbitrary things at compile time (e.g. call make)

  • More tooling later on…

Snappy

  • Snappy is a compression and decompression library

  • Written in C++

  • With a C API

  • Hat tip to the Rust book

  • We’ll write a small Rust program to compress and decompress files using Snappy

Snappy Step 2: Declare Extern Functions from Snappy

Snappy Step 3: Compress and Decompress Files with Raw FFI

Snappy Step 4: Write a Safe, Rusty Interface to Snappy

Snappy Step 5: Use bindgen Instead of Writing Extern Blocks by Hand

Snappy: Takeaways

  • Put building and linking in build.rs and forget about it

  • Don’t write extern FFI bindings by hand: automate the process with bindgen

  • Safe, Rusty wrapper APIs:

    • Study and learn the library’s invariants
      • Who malloc s which things?
      • Who free s them?
      • Which pointers can be NULL?
      • When is it valid to use this pointer? For how long?
    • Push those invariants into the type system as much as possible
    • When that is not an option, add a liberal sprinkling of asserts!

Safe, Rusty APIs Wrapping FFI

  • std is full of great examples!

  • Use the [src], Luke

Safe, Rusty APIs Wrapping FFI

  • Use RAII
    • Resource Acquisition Is Initialization
    • Constructor makes FFI call that allocates a resource
    • The Drop implementation makes the FFI call to deallocate the resource
    • Example: std::fs::File

Safe, Rusty APIs Wrapping FFI

struct Raii {
    raw: *mut raw_ffi_type,
}

impl Raii {
    fn new() -> Raii {
        Raii {
            raw: unsafe {
                ffi_allocate()
            }
        }
    }
}

impl Drop for Raii {
    fn drop(&mut self) {
        unsafe {
            ffi_deallocate(self.raw)
        }
    }
}

Safe, Rusty APIs Wrapping FFI

{
    let resource = Raii::new();
    ...
    resource.do_stuff();
    ...
    // Automatically cleaned up at the end of scope
}

Safe, Rusty APIs Wrapping FFI

  • Do not use RAII
    • If calling ffi_deallocate is CRITICAL
    • E.g. not calling it results in memory unsafety
    • Also types that cannot move, but you don’t want to Box
    • Instead, use a closure + catch_unwind + cleanup instead
    • Example: scoped threads

Safe, Rusty APIs Wrapping FFI

struct Resource {
    raw: *mut raw_ffi_type,
}

impl Resource {
    fn with<F, T>(f: F) -> T
        where F: FnOnce(&mut Resource) -> T
    {
        let mut r = Resource {
            raw: unsafe { ffi_allocate() },
        };
        let res = std::panic::catch_unwind(
            std::panic::AssertUnwindSafe(|| f(&mut r)));
        unsafe { ffi_deallocate(r.raw); }
        match res {
            Err(e) => std::panic::resume_unwind(e),
            Ok(t) => t,
        }
    }
}

Safe, Rusty APIs Wrapping FFI

Resource::with(|resource| {
    ...
    resource.do_stuff();
    ...
})

Rust from Other Languages Step 1: Build Your Crate as a Shared Library

Rust from Other Languages Step 2: Passing String Arguments

Rust from Other Languages Step 3: Giving Away Ownership of Resources

Rust from Other Languages: Next Steps

  • Next, we would
    • In Python, wrap the Rust FFI in a Pythonic API
    • In Rust, use the cpython crate
  • But, we’re going to stop here

Rust from Other Languages: Takeaways

  • Inside Cargo.toml:
[lib]
name = "whatever"
crate-type = ["dylib"]

Rust from Other Languages: Takeaways

  • To export structs:
#[repr(C)]
pub struct MyType {
    ...
}

Rust from Other Languages: Takeaways

  • To export functions:
#[no_mangle]
pub extern fn new_my_type() -> *mut MyType {
    let result = std::panic::catch_unwind(|| {
        ...
    });
    match result {
        Ok(r) => r,
        Err(_) => std::ptr::null_mut(),
    }
}

Using Rust from X

  • rusty-cheddar crate to generate C header files

  • cpython crate for Python

  • ruru and helix crates for Ruby

  • neon crate for NodeJS

  • rustler crate for Erlang

  • And more…

More Rust and FFI Learning Resources

fin