Extendr is a Rust extension mechanism for R
The following code illustrates a simple structure trait which is written in Rust. The data is defined in the struct
declaration and the methods in the impl
.
Extendr consists of the following projects:
extendr
set of crates, including:
extendr-api
- the core Extendr crate providing all of the functionality;extendr-macros
- Extendr crate responsbile for Rust wrapper generation;extendr-engine
- crate that enables launching R sessions from Rust code;rextendr
- an R package that helps scaffolding extendr-enabled packages or compiling Rust code dynamically;libR-sys
- provides auto-generated R bindings for Rust.use extendr_api::prelude::*;
struct Person {
pub name: String,
}
#[extendr]
impl Person {
fn new() -> Self {
Self { name: "".to_string() }
}
fn set_name(&mut self, name: &str) {
self.name = name.to_string();
}
fn name(&self) -> &str {
self.name.as_str()
}
}
#[extendr]
fn aux_func() {
}
// Macro to generate exports
extendr_module! {
mod classes;
impl Person;
fn aux_func;
}
The #[extendr]
attribute causes the compiler to generate wrapper and registration functions for R which are called when the package is loaded.
On R’s side, users can access to the above Rust functions as follows:
# call function
aux_func()
# create Person object
p <- Person$new()
p$set_name("foo")
p$name() # "foo" is returned
The extendr_module!
macro lists the module name and exported functions and interfaces.
This library aims to provide an interface that will be familiar to first-time users of Rust or indeed any compiled language.
Anyone who knows the R library should be able to write R extensions.
Extendr provides a number of wrappers for R types. These fall into three categories, scalar types such as a single integer, vector types which are an array of a scalar type and linked list types used to represent R code and call arguments.
R type | Extendr wrapper | Deref type: &*object
|
---|---|---|
Any |
extendr_api::robj::Robj |
N/A |
character |
extendr_api::wrapper::Rstr |
N/A |
integer |
extendr_api::wrapper::Rint |
N/A |
double |
extendr_api::wrapper::Rfloat |
N/A |
complex |
extendr_api::wrapper::Rcplx |
N/A |
extptr |
extendr_api::wrapper::ExternalPtr<T> |
&T / &mut T
|
R type | Extendr wrapper | Deref type: &*object
|
---|---|---|
integer |
extendr_api::wrapper::Integers |
&[Rint] |
double |
extendr_api::wrapper::Doubles |
&[Rfloat] |
logical |
extendr_api::wrapper::Logicals |
&[Rbool] |
complex |
extendr_api::wrapper::Complexes |
&[Rcplx] |
string |
extendr_api::wrapper::Strings |
&[Rstr] |
list |
extendr_api::wrapper::List |
&[Robj] |
data.frame |
extendr_api::wrapper::Dataframe<T> |
&[Robj] |
expression |
extendr_api::wrapper::Expression |
&[Lang] |
R type | Extendr wrapper | Deref type: &*object
|
---|---|---|
pairlist |
extendr_api::wrapper::Pairlist |
N/A |
lang |
extendr_api::wrapper::Lang |
N/A |
Lists and strings in rust are vectors of R objects. These are represented by the wrappers List
and Strings
.
List
contains a slice of Robj
wrappers which can contain any R object.
Strings
contains a slice of Rstr
wrappers which can contain a single string.
These examples show how to return a list or string to R from Rust.
Whilst we can use i32
and f64
in Extendr to return values. A better way is to use the Rint
and Rfloat
wrappers which provide access to the NA
value used by R to represent missing data.
We can use Extendr to take advantage of the stats and plotting functions in R.
For example, we could make a web server that returns plots of incoming data.
use extendr_api::{test, Result, eval_string, eval_string_with_params};
use extendr_api::{Doubles, R};
fn main() {
test!{
let x = Doubles::from_values((0..100).map(|i| i as f64 / 20.0));
// let y = Doubles::from_values(x.iter().map(|x| x.inner().sin()));
let y = Doubles::from_values((0..100).map(|i| (i as f64 / 20.0).sin()));
// Set a PNG device
R!(r#"png("/tmp/sin_plot.png")"#)?;
// Plot x and y
R!("plot({{&x}}, {{&y}})")?;
// Linear model.
R!("abline(lm({{y}} ~ {{x}}))")?;
// Flush the device to the image.
R!("dev.off()")?;
}
}