# FFI::Platypus::Lang::Rust ![static](https://github.com/PerlFFI/FFI-Platypus-Lang-Rust/workflows/static/badge.svg) ![linux](https://github.com/PerlFFI/FFI-Platypus-Lang-Rust/workflows/linux/badge.svg)
Documentation and tools for using Platypus with the Rust programming language
# SYNOPSIS
Rust:
```
#![crate_type = "cdylib"]
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
a + b
}
```
Perl:
```perl
use FFI::Platypus 2.00;
use FFI::CheckLib qw( find_lib_or_die );
use File::Basename qw( dirname );
my $ffi = FFI::Platypus->new( api => 2, lang => 'Rust' );
$ffi->lib(
find_lib_or_die(
lib => 'add',
libpath => [dirname __FILE__],
systempath => [],
)
);
$ffi->attach( add => ['i32', 'i32'] => 'i32' );
print add(1,2), "\n"; # prints 3
```
# DESCRIPTION
This module provides native Rust types for [FFI::Platypus](https://metacpan.org/pod/FFI::Platypus) in order to
reduce cognitive load and concentrate on Rust and forget about C types.
This document also documents issues and caveats that I have discovered
in my attempts to work with Rust and FFI.
Note that in addition to using pre-compiled Rust libraries, you can
bundle Rust code with your Perl distribution using [FFI::Build](https://metacpan.org/pod/FFI::Build) and
[FFI::Build::File::Cargo](https://metacpan.org/pod/FFI::Build::File::Cargo).
# EXAMPLES
The examples in this discussion are bundled with this distribution and
can be found in the `examples` directory.
## Passing and Returning Integers
### Rust Source
```
#![crate_type = "cdylib"]
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
a + b
}
```
### Perl Source
```perl
use FFI::Platypus 2.00;
use FFI::CheckLib qw( find_lib_or_die );
use File::Basename qw( dirname );
my $ffi = FFI::Platypus->new( api => 2, lang => 'Rust' );
$ffi->lib(
find_lib_or_die(
lib => 'add',
libpath => [dirname __FILE__],
systempath => [],
)
);
$ffi->attach( add => ['i32', 'i32'] => 'i32' );
print add(1,2), "\n"; # prints 3
```
### Execute
```
$ rustc add.rs
$ perl add.pl
3
```
### Notes
Basic types like integers and floating points are the easiest to pass
across the FFI boundary. The Platypus Rust language plugin (this module)
provides the basic types used by Rust (for example: `bool`, `i32`, `u64`,
`f64`, `isize` and others) will all work as a Rust programmer would expect.
This is nice because you don't have to think about what the equivalent types
would be in C when you are writing your Perl extension in Rust.
Rust symbols are "mangled" by default, which means that you cannot use
the name of the function from the source code without knowing what the
mangled name is. Rust provides a function attribute `#[no_mangle]`
which will tell the compiler not to mangle the name, making lookup of
the symbol possible from other programming languages like Perl.
Rust functions do not use the same ABI as C by default, so if you want
to be able to call Rust functions from Perl they need to be declared
as `extern "C"` as in this example.
We also set the "crate type" to `cdylib` in the first line to tell the
Rust compiler to generate a dynamic library that will be consumed by
a non-Rust language like Perl.
## String Arguments
### Rust Source
```perl
#![crate_type = "cdylib"]
use std::ffi::CStr;
use std::os::raw::c_char;
#[no_mangle]
pub extern "C" fn how_many_characters(s: *const c_char) -> isize {
if s.is_null() {
return -1;
}
let s = unsafe { CStr::from_ptr(s) };
match s.to_str() {
Ok(s) => s.chars().count() as isize,
Err(_) => -2,
}
}
```
### Perl Source
```perl
use FFI::Platypus 2.00;
use FFI::CheckLib qw( find_lib_or_die );
use File::Basename qw( dirname );
my $ffi = FFI::Platypus->new( api => 2, lang => 'Rust' );
$ffi->lib(
find_lib_or_die(
lib => 'argument',
libpath => [dirname __FILE__],
systempath => [],
)
);
$ffi->attach( how_many_characters => ['string'] => 'isize' );
print how_many_characters(undef), "\n"; # prints -1
print how_many_characters("frooble bits"), "\n"; # prints 12
```
### Execute
```
$ rustc argument.rs
$ perl argument.pl
-1
12
```
### Notes
Strings are considerably more complicated for a number of reasons,
but for passing them into Rust code the main challenge is that the
representation is different from what C uses. C Uses NULL terminated
strings and Rust uses a pointer and size combination that allows
NULLs inside strings. Perls internal representation of strings is
actually closer to what Rust uses, but when Perl talks to other
languages it typically uses C Strings.
Getting a Rust string slice `&str` requires a few stems
- We have to ensure the C pointer is not `NULL`
We return `-1` to indicate an error here. As we can see from the
calling Perl code passing an `undef` from Perl is equivalent to
passing in `NULL` from C.
- Wrap using `Cstr`
We then wrap the pointer using an `unsafe` block. Even though
we know at this point that the pointer cannot be `NULL` it could
technically be pointing to uninitialized or unaddressable memory.
This `unsafe` block is unfortunately necessary, though it is
relatively isolated so it is easy to reason about and review.
- Convert to UTF-8
If the string that we passed in is valid UTF-8 we can convert
it to a `&str` using `to_str` and compute the length of the
string. Otherwise, we return -2 error.
(This example is based on one provided in the
[Rust FFI Omnibus](http://jakegoulding.com/rust-ffi-omnibus/string_arguments/))
## Returning allocated strings
### Rust Source
```perl
#![crate_type = "cdylib"]
use std::ffi::CString;
use std::iter;
use std::os::raw::c_char;
#[no_mangle]
pub extern "C" fn theme_song_generate(length: u8) -> *mut c_char {
let mut song = String::from(" ");
song.extend(iter::repeat("na ").take(length as usize));
song.push_str("Batman! ");
let c_str_song = CString::new(song).unwrap();
c_str_song.into_raw()
}
#[no_mangle]
pub extern "C" fn theme_song_free(s: *mut c_char) {
if s.is_null() {
return;
}
unsafe { CString::from_raw(s) };
}
```
### Perl Source
```perl
use FFI::Platypus 2.00;
use FFI::CheckLib qw( find_lib_or_die );
use File::Basename qw( dirname );
my $ffi = FFI::Platypus->new( api => 2, lang => 'Rust' );
$ffi->lib(
find_lib_or_die(
lib => 'return',
libpath => [dirname __FILE__],
systempath => [],
)
);
$ffi->attach( theme_song_free => ['opaque'] => 'void' );
$ffi->attach( theme_song_generate => ['u8'] => 'opaque' => sub {
my($xsub, $length) = @_;
my $ptr = $xsub->($length);
my $str = $ffi->cast( 'opaque' => 'string', $ptr );
theme_song_free($ptr);
$str;
});
print theme_song_generate(42), "\n";
```
### Execute
```
$ rustc return.rs
$ perl return.pl
na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na Batman!
```
### Notes
The big challenge of returning strings from Rust into Perl is
handling the ownership. In this example we have a C API implemented
in Rust that returns a C NULL terminated string, but we have to
pass it back into Rust in order to deallocate it when we are done.
Unfortunately Platypus' `string` type assumes that the callee
retains ownership of the returned string, so we have to get the
pointer instead as an `opaque` so that we can later free it.
Before freeing it though we cast it into a Perl string.
In order to hide the complexities from caller of our
`theme_song_generate` function, we use a function wrapper to
do all of that for us.
(This example is based on one provided in the
[Rust FFI Omnibus](http://jakegoulding.com/rust-ffi-omnibus/string_return/))
## Returning allocated strings, but keeping ownership
### Rust Source
```perl
#![crate_type = "cdylib"]
use std::cell::RefCell;
use std::ffi::CString;
use std::iter;
use std::os::raw::c_char;
#[no_mangle]
pub extern "C" fn theme_song_generate(length: u8) -> *const c_char {
thread_local! {
static KEEP: RefCell