-
Notifications
You must be signed in to change notification settings - Fork 23
Rusty Platypus
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);
}
}