extendr-api
A safe and user friendly R extension interface.
- Build rust extensions to R.
- Convert R packages to Rust crates.
This library aims to provide an interface that will be familiar to
first-time users of Rust or indeed any compiled language.
See [Robj] for much of the content of this crate.
[Robj] provides a safe wrapper for the R object type.
Use attributes and macros to export to R.
use extendr_api::prelude::*;
#[extendr]
fn fred(a: i32) -> i32 {
a + 1
}
extendr_module! {
mod mymodule;
fn fred;
}
In R:
result <- fred(1)
[Robj] is a wrapper for R objects.
The r!() and R!() macros let you build R objects
using Rust and R syntax respectively.
use extendr_api::prelude::*;
test! {
let character = r!("hello");
let character = r!(["hello", "goodbye"]);
let integer = r!(1);
let real = r!(1.0);
let real_vector = r!([1.0, 2.0]);
let real_vector = &[1.0, 2.0].iter().collect_robj();
let real_vector = r!(vec![1.0, 2.0]);
let function = R!("function(x, y) { x + y }")?;
let list = list!(a = 1, b = 2);
let list = r!(List::from_values(vec![1, 2, 3]));
let list = r!(List::from_values(vec!["a", "b", "c"]));
let list = r!(List::from_values(&[r!("a"), r!(1), r!(2.0)]));
let sym = sym!(wombat);
let vector = (0..3).map(|x| x * 10).collect_robj();
}
In Rust, we prefer to use iterators rather than loops.
use extendr_api::prelude::*;
test! {
let res = r!(1 ..= 100);
assert_eq!(res, R!("1:100")?);
let res = r!(0 .. 100);
assert_eq!(res.len(), 100);
let iter = (0..3).map(|i| format!("fred{}", i));
let character = iter.collect_robj();
assert_eq!(character, r!(["fred0", "fred1", "fred2"]));
}
To index a vector, first convert it to a slice and then
remember to use 0-based indexing. In Rust, going out of bounds
will cause and error (a panic) unlike C++ which may crash.
use extendr_api::prelude::*;
test! {
let vals = r!([1.0, 2.0]);
let slice = vals.as_real_slice().ok_or("expected slice")?;
let one = slice[0];
let two = slice[1];
assert_eq!(one, 1.0);
assert_eq!(two, 2.0);
}
Much slower, but more general are these methods:
use extendr_api::prelude::*;
test! {
let vals = r!([1.0, 2.0, 3.0]);
assert_eq!(vals.index(1)?, r!(1.0));
assert_eq!(vals.slice(1..=2)?, r!([1.0, 2.0]));
let list = list!(a = 1.0, b = "xyz");
assert_eq!(list.dollar("a")?, r!(1.0));
}
The [R!] macro lets you embed R code in Rust
and takes Rust expressions in {{ }} pairs.
The [Rraw!] macro will not expand the {{ }} pairs.
use extendr_api::prelude::*;
test! {
assert_eq!(R!("1 + 1")?, r!(2.0));
let a = 1.0;
assert_eq!(R!("1 + {{a}}")?, r!(2.0));
assert_eq!(R!(r"
x <- {{ a }}
x + 1
")?, r!(2.0));
assert_eq!(R!(r#"
x <- "hello"
x
"#)?, r!("hello"));
assert_eq!(Rraw!(r"
x <- {{ 1 }}
x + 1
")?, r!(2.0));
}
The [r!] macro converts a rust object to an R object
and takes parameters.
use extendr_api::prelude::*;
test! {
let one = 1.0;
assert_eq!(r!(one+1.0), r!(2.0));
}
You can call R functions and primitives using the [call!] macro.
use extendr_api::prelude::*;
test! {
let confint1 = R!(confint(lm(weight ~ group - 1, PlantGrowth)))?;
let formula = call!("~", sym!(weight), lang!("-", sym!(group), 1))?;
let plant_growth = global!(PlantGrowth)?;
let model = call!("lm", formula, plant_growth)?;
let confint2 = call!("confint", model)?;
assert_eq!(confint1.as_real_vector(), confint2.as_real_vector());
}
Rust has a concept of "Owned" and "Borrowed" objects.
Owned objects, such as [Vec] and [String] allocate memory
which is released when the object lifetime ends.
Borrowed objects such as &[i32] and &str are just pointers
to annother object's memory and can't live longer than the
object they reference.
Borrowed objects are much faster than owned objects and use less
memory but are used only for temporary access.
When we take a slice of an R vector, for example, we need the
original R object to be alive or the data will be corrupted.
use extendr_api::prelude::*;
test! {
let robj = r!([1, 2, 3]);
let slice = robj.as_integer_slice().ok_or("expected slice")?;
assert_eq!(slice.len(), 3);
}
License: MIT