extendr - A safe and user friendly R extension interface using Rust

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.

Wrappers for R types

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.

Scalar types

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

Vector types

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]

Linked list types

R type Extendr wrapper Deref type: &*object
pairlist extendr_api::wrapper::Pairlist N/A
lang extendr_api::wrapper::Lang N/A

Examples

Returning lists and strings

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.

use extendr_api::wrapper::{List, Strings};
use extendr_api::list;

fn get_strings() -> Strings {
    Strings::from_values(
        (0..10)
        .map(|i| format!("number {}", i))
    )
}

fn get_named_list() -> List {
    list!(x=1, y="xyz", z=())
}

fn get_unnamed_list() -> List {
    List::from_values(0..10)
}

Returning scalars

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.

use extendr_api::scalar::{Rint, Rfloat};

// for .na()
use extendr_api::CanBeNA;

fn get_int() -> Rint {
    Rint::from(1)
}

fn get_na_int() -> Rint {
    Rint::na()
}

fn get_float() -> Rfloat {
    Rfloat::from(1.0)
}

fn get_na_float() -> Rfloat {
    Rfloat::na()
}

Plotting a PNG file from Rust

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()")?;
    }
}

  • Extendr logo

License

Dev status

  • Github Actions Build Status
  • Crates.io
  • Documentation
  • License: MIT

Developers

  • Andy Thomason
    Author, maintainer
  • Mossa M. Reimert
    Author
  • Claus O. Wilke
    Author
  • Hiroaki Yutani
    Author
  • Ilia Kosenkov
    Author
  • Daniel Falbel
    Author
  • Genomics Plc
    Copyright holder