Skip to content
Graham Ollis edited this page Nov 25, 2019 · 4 revisions

In a separate article I mentioned that you can use FFI::Platypus's bundling interface to write Perl extensions and bindings to languages other than C. Lets see how that actually works by trying to write a Perl extension in Rust. Writing your extension in Rust has some advantages over writing them in C. For one thing Rust has a lot of memory safety built into the language, so it is much harder to write code with memory errors. For another, Rust has a much more sophisticated module systems (the rust community calls there modules "crates") managed by a tool called C. This means that you can use the Rust standard library or one of the many crates as dependencies in writing your extension, and let C handle the heavy lifting.

To start lets create a new Perl dist called C with some bundled Rust code in the ffi directory. (For those who remember Bundling With Platypus, we did the same thing in C).

$ mkdir Person
$ cd Person
$ cargo new --lib --name person ffi
      Created library `person` package

We need to make sure that cargo builds a dynamic library, because that is the only sort of library that we can use from Perl using Platypus. Edit ffi/Cargo.toml, and add this stanza to the configuration.

[lib]
crate-type = ["dylib"]

Cargo will have created a ffi/src/lib.rs file from a template for you. Here is the person library that we wrote in C, this time in Rust. (May not be ideomatic Rust, I'm still learning the language).

use std::ffi::CString;
use std::ffi::c_void;
use std::ffi::CStr;
use std::cell::RefCell;

struct Person {
    name: String,
    lucky_number: i32,
}

impl Person {
    fn new(name: &str, lucky_number: i32) -> Person {
        Person {
            name: String::from(name),
            lucky_number: lucky_number,
        }
    }

    fn get_name(&self) -> String {
        String::from(&self.name)
    }

    fn get_lucky_number(&self) -> i32 {
        self.lucky_number
    }
}

type CPerson = c_void;

#[no_mangle]
pub extern "C" fn person_new(_class: *const i8, name: *const i8, lucky_number: i32) -> *mut CPerson {
    let name = unsafe { CStr::from_ptr(name) };
    let name = name.to_string_lossy().into_owned();
    Box::into_raw(Box::new(Person::new(&name, lucky_number))) as *mut CPerson
}

#[no_mangle]
pub extern "C" fn person_name(p: *mut CPerson) -> *const i8 {
    thread_local! (
        static KEEP: RefCell<Option<CString>> = RefCell::new(None);
    );

    let p = unsafe { &*(p as *mut Person)};
    let name = CString::new(p.get_name()).unwrap();
    let ptr = name.as_ptr();
    KEEP.with(|k| {
        *k.borrow_mut() = Some(name);
    });
    ptr
}

#[no_mangle]
pub extern "C" fn person_lucky_number(p: *mut CPerson) -> i32 {
    let p = unsafe { &*(p as *mut Person) };
    p.get_lucky_number()
}

#[allow(non_snake_case)]
#[no_mangle]
pub extern "C" fn person_DESTROY(p: *mut CPerson) {
    unsafe { drop(Box::from_raw(p as *mut Person)) };
}


#[cfg(test)]
mod tests {

    use std::ffi::CString;
    use std::ffi::CStr;

    #[test]
    fn rust_lib_works() {
        let name = "Graham Ollis";
        let plicease = crate::Person::new(name, 42);
        assert_eq!(plicease.get_name(), "Graham Ollis");
        assert_eq!(plicease.get_lucky_number(), 42);
    }

    #[test]
    fn c_lib_works() {
        let class = CString::new("Person");
        let name = CString::new("Graham Ollis");
        let plicease = crate::person_new(class.unwrap().as_ptr(), name.unwrap().as_ptr(), 42);
        assert_eq!(unsafe { CStr::from_ptr(crate::person_name(plicease)).to_string_lossy().into_owned() },  "Graham Ollis");
        assert_eq!(crate::person_lucky_number(plicease), 42);
    }
}

There is a lot to unpack here, but basically we've implemented a private Person struct in Rust, with two methods that get name and lucky number attributes for th Person's name and lucky number. We've also written some tests so that we can test the crate to make sure that it works as expected.

What's interesting from an FFI perspective is that we've defined a FFI/C API interface for the Person calss. We use what C calls a void * pointer, and what we call in Platypus an opaque type to represent that class in FFI.

type CPerson = c_void;

Now we can define constructors and accessor methods in the same way that we did in C.

#[no_mangle]
pub extern "C" fn person_new(_class: *const i8, name: *const i8, lucky_number: i32) -> *mut CPerson

The #[no_mangle] and pub extern "C" tell the Rust compiler to use the C ABI, and unmangled names so these functions can be called from C or FFI.

The body of the constructor takes C types and creates a new instance of the Rust Person struct, which uses the native Rust types internally. There is some unsafe code here because we are interfacing with

Clone this wiki locally