Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are the steps necessary to pass R objects to a Rust program?

Tags:

r

rust

Both R and Rust can interface with C code, so I think it is very possible. I am a bit unclear about how to proceed, however.

I have read these sections looking for answers:

  1. R-extensions System-and-foreign-language-interfaces
  2. The Rust foreign function interface guide

But while I am well-versed in R I am not a systems programmer and confused by what the build-chain looks like for such an endeavor.

Using Rinternals.h would be ideal, but I would settle for the simpler .C interface as well.

like image 549
Zelazny7 Avatar asked Mar 06 '15 14:03

Zelazny7


2 Answers

I have been struggling on this for a while as well, but once you know how I'ts actually not that difficult.

First create a Rust library following these instructions: rust-inside-other-languages. Here is an example Rust library:

//src/lib.rs

#[no_mangle]
pub fn kelvin_to_fahrenheit(n: f64) -> f64 {
    n * 9.0/5.0 - 459.67
}

If you follow the instructions in rust-inside-other-languages, then you should be able to generate a *.so (or *.dll or .dylib, depending on your system). Let's presume this compiled file is called libtempr.so.

Now create a C++ file which will pass the functions you need to R:

//embed.cpp

extern "C" {
    double kelvin_to_fahrenheit(double);
}

// [[Rcpp::export]]
double cpp_kelvin_to_fahrenheit(double k) {
  double f = kelvin_to_fahrenheit(k);
  return(f);
}

Now before starting R, make sure the environment variable LD_LIBRARY_PATH contains the directory where the shared object generated previously (libtempr.so) is stored. In the shell do:

$ export LD_LIBRARY_PATH=/home/sam/path/to/shared/object:$LD_LIBRARY_PATH
$ rstudio # I highly recommend using Rstudio for your R coding

Finally in Rstudo, write this file:

library(Rcpp)

Sys.setenv("PKG_LIBS"="-L/home/sam/path/to/shared/object -ltempr")

sourceCpp("/home/sam/path/to/embed.cpp", verbose = T, rebuild = T)

cpp_kelvin_to_fahrenheit(300)
  • Be careful that in Sys.setenv the -L option points to the directory containing your Rust shared object.
  • Also be careful that -l option is the name of your shared object without the lib prefix and without the .so (or whatever it is on your system) postfix.
  • Using Sys.setenv in R to set the LD_LIBRARY_PATH variable DOES NOT WORK. Export the variable before starting R.
  • The verbose option is there so that you can see what Rcpp does to compile your C++ file. Notice how the options in PKG_LIBS above are used for compiling your C++ file.
  • The rebuild options is there to force a rebuild of the C++ file every time you run this line of R code.

If you did everything well, then run the R file above in the interactive console and it should output 80.33 when you reach the last line.

If anything is not clear, ask in the comments, and I'll try to improve my answer.

Hope it helped :)


Final note, the base functions dyn.load and .C can be used as an alternative approach. But this requires writing a lot more boilerplate wrapper code than this approach.

like image 65
Sam De Meyer Avatar answered Oct 27 '22 01:10

Sam De Meyer


If R can interface with C code, so it is no problem at all to compile shared library from Rust code which exposes C-style functions.

Then you can easily use your library as it was written in C or C++. Of course, you will not able to use Rust object and libraries directly from R, you will have to make appropriate C interface for converting their functions.

Here is how can I do that for SBCL, and I suppose it would be very similar for R:

On Rust side

Some code:

% cat experiment.rs

extern crate libc;

use libc::{c_int, c_char};
use std::{ffi, str};

#[no_mangle]
pub extern fn rust_code_string_to_int(s: *const c_char, r: *mut c_int) -> c_int { 
    let string = String::from_utf8_lossy(unsafe { ffi::CStr::from_ptr(s).to_bytes() });
    match <isize as str::FromStr>::from_str(&*string) {
        Ok(value) => { unsafe { *r = value as c_int }; 0 },
        Err(_) => -1,
    }
}

Then I'm making shared lib:

% rustc --crate-type dylib experiment.rs
% nm -a libexperiment.dylib | grep rust_code_string_to_int
0000000000001630 t __ZN23rust_code_string_to_int10__rust_abiE
00000000000015e0 T _rust_code_string_to_int

Next, on SBCL side

Now I'm just loading my shared lib and then I have access to my rust_code_string_to_int function:

RUST> (sb-alien:load-shared-object "libexperiment.dylib")
#P"libexperiment.dylib"
RUST> (sb-alien:with-alien ((result sb-alien:int 0))  
          (values (sb-alien:alien-funcall (sb-alien:extern-alien "rust_code_string_to_int" 
                                                                 (sb-alien:function sb-alien:int 
                                                                                    (sb-alien:c-string :external-format :utf-8)
                                                                                    (sb-alien:* sb-alien:int)))
                                          (sb-alien:make-alien-string "42")
                                          (sb-alien:addr result))
                  result))
0
42
like image 33
swizard Avatar answered Oct 27 '22 00:10

swizard