Expand description
An ergonomic, opinionated, safe and user-friendly wrapper to the R-API
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.
§Examples
Use attributes and macros to export to R.
use extendr_api::prelude::*;
// Export a function or impl to R.
#[extendr]
fn fred(a: i32) -> i32 {
a + 1
}
// define exports using extendr_module
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! {
// An R object with a single string "hello"
let character = r!("hello");
let character = r!(["hello", "goodbye"]);
// An R integer object with a single number 1L.
// Note that in Rust, 1 is an integer and 1.0 is a real.
let integer = r!(1);
// An R real object with a single number 1.
// Note that in R, 1 is a real and 1L is an integer.
let real = r!(1.0);
// An R real vector.
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]);
// An R function object.
let function = R!("function(x, y) { x + y }")?;
// A named list using the list! macro.
let list = list!(a = 1, b = 2);
// An unnamed list (of R objects) using the List wrapper.
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)]));
// A symbol
let sym = sym!(wombat);
// A R vector using collect_robj()
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! {
// 1 ..= 100 is the same as 1:100
let res = r!(1 ..= 100);
assert_eq!(res, R!("1:100")?);
// Rust arrays are zero-indexed so it is more common to use 0 .. 100.
let res = r!(0 .. 100);
assert_eq!(res.len(), 100);
// Using map is a super fast way to generate vectors.
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];
// let error = slice[2];
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]);
// one-based indexing [[i]], returns an object.
assert_eq!(vals.index(1)?, r!(1.0));
// one-based slicing [x], returns an object.
assert_eq!(vals.slice(1..=2)?, r!([1.0, 2.0]));
// $ operator, returns an object
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! {
// The text "1 + 1" is parsed as R source code.
// The result is 1.0 + 1.0 in Rust.
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"));
// Use the R meaning of {{ }} and do not expand.
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! {
// The text "1.0+1.0" is parsed as Rust source code.
let one = 1.0;
assert_eq!(r!(one+1.0), r!(2.0));
}
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! {
// robj is an "Owned" object that controls the memory allocated.
let robj = r!([1, 2, 3]);
// Here slice is a "borrowed" reference to the bytes in robj.
// and cannot live longer than robj.
let slice = robj.as_integer_slice().ok_or("expected slice")?;
assert_eq!(slice.len(), 3);
}
§Writing tests
To test the functions exposed to R, wrap your code in the test!
macro.
This macro starts up the necessary R machinery for tests to work.
use extendr_api::prelude::*;
#[extendr]
fn things() -> Strings {
Strings::from_values(vec!["Test", "this"])
}
// define exports using extendr_module
extendr_module! {
mod mymodule;
fn things;
}
#[cfg(test)]
mod test {
use super::*;
use extendr_api::prelude::*;
#[test]
fn test_simple_function() {
assert_eq!(things().elt(0), "Test")
}
}
§Returning Result<T, E>
to R
Two experimental features for returning error-aware R list
s, result_list
and result_condition
,
can be toggled to avoid panics on Err
. Instead, an Err
x
is returned as either
- list:
list(ok=NULL, err=x)
whenresult_list
is enabled, - error condition:
<error: extendr_error>
, withx
placed incondition$value
, whenresultd_condition
is enabled.
It is currently solely up to the user to handle any result on R side.
There is an added overhead of wrapping Rust results in an R list
object.
use extendr_api::prelude::*;
// simple function always returning an Err string
#[extendr]
fn oups(a: i32) -> std::result::Result<i32, String> {
Err("I did it again".to_string())
}
// define exports using extendr_module
extendr_module! {
mod mymodule;
fn oups;
}
In R:
oups(1)
> ... long panic traceback from rust printed to stderr
lst <- oups(1)
print(lst)
> list(ok = NULL, err = "I did it again")
cnd <- oups(1)
print(cnd)
> <error: extendr_error>
print(cnd$value)
> "I did it again"
oups_handled <- function(a) {
val_or_err <- oups(1)
if (inherits(val_or_err, "extendr_error")) stop(val_or_err)
val_or_err
}
§Feature gates
extendr-api has some optional features behind these feature gates:
ndarray
: provides the conversion between R’s matrices andndarray
.num-complex
: provides the conversion between R’s complex numbers andnum-complex
.serde
: provides theserde
support.graphics
: provides the functionality to control or implement graphics devices.either
: provides implementation of type conversion traits forEither<L, R>
fromeither
ifL
andR
both implement those traits.faer
: provides conversion between R’s matrices andfaer
.
extendr-api supports three ways of returning a Result<T,E> to R. Only one behavior feature can be enabled at a time.
result_panic
: Default behavior, returnOk
as is, panic! on anyErr
Default behavior can be overridden by specifying extend_api
features, i.e. extendr-api = {..., default-features = false, features= ["result_condition"]}
These features are experimental and are subject to change.
result_list
: returnOk
aslist(ok=?, err=NULL)
orErr
list(ok=NULL, err=?)
result_condition
: returnOk
as is orErr
as $value in an R error condition.
Re-exports§
pub use robj::Robj;
pub use thread_safety::catch_r_error;
pub use thread_safety::handle_panic;
pub use thread_safety::single_threaded;
pub use thread_safety::throw_r_error;
pub use metadata::Metadata;
pub use error::*;
pub use functions::*;
pub use lang_macros::*;
pub use na::*;
pub use robj::*;
pub use wrapper::*;
Modules§
- Convert R objects to a wide variety of types.
- Error handling in Rust called from R.
- Graphic Device Operations
- Argument parsing and checking.
- Module metadata
- A set of optional features and third-party crate integrations, usually hidden behind feature gates.
- Maintain ownership of R objects.
- Common exports for extendr-api.
- rmacros - a set of macros to call actual R functions in a rusty way.
- R object handling.
- Provide limited protection for multithreaded access to the R API.
- Wrappers are lightweight proxies for references to R datatypes. They do not contain an Robj (see array.rs for an example of this).
Macros§
- Execute R code by parsing and evaluating tokens.
- Execute R code by parsing and evaluating tokens but without expanding parameters.
- Call a function or primitive defined by a text expression with arbitrary parameters. This currently works by parsing and evaluating the string in R, but will probably acquire some shortcuts for simple expressions, for example by caching symbols and constant values.
- Create a dataframe.
- Define a module and export symbols to R Example:
- Create a factor.
- Get a global variable.
- A macro for constructing R language objects.
- Create a List R object from a list of name-value pairs.
- Create a Pairlist R object from a list of name-value pairs.
- Convert a rust expression to an R object.
- Print via the R error stream.
- Print with a newline via the R output stream.
- Print via the R output stream.
- Print with a newline via the R output stream.
- The sym! macro install symbols. You should cache your symbols in variables as generating them is costly.
- Macro for running tests.
- Get a local variable from the calling function or a global variable if no such variable exists.
Enums§
- Enum use to unpack R objects into their specialist wrappers.
- Type of R objects used by Robj::rtype.
Constants§
- FALSE value eg.
r!(FALSE)
- NA value for integers eg.
r!(NA_INTEGER)
- NA value for logical.
r!(NA_LOGICAL)
- NA value for real values eg.
r!(NA_REAL)
- NA value for strings.
r!(NA_STRING)
- NULL value eg.
r!(NULL)
- TRUE value eg.
r!(TRUE)
Traits§
- Used for immutable dereferencing operations, like
*v
. - Used for mutable dereferencing operations, like in
*v = 1;
. - Simple and safe type conversions that may fail in a controlled way under some circumstances. It is the reciprocal of
TryInto
. - An attempted conversion that consumes
self
, which may or may not be expensive.
Functions§
- make_
method_ 🔒 ⚠def - Convert extendr’s Rtype to R’s SEXPTYPE. Panics if the type is Unknown.
- Convert R’s SEXPTYPE to extendr’s Rtype.
Attribute Macros§
- The
#[extendr]
-macro may be placed on three items
Derive Macros§
- Enable the construction of dataframes from arrays of structures.
- Derives an implementation of
From<Struct> for Robj
andFrom<&Struct> for Robj
on this struct. - Derives an implementation of
TryFrom<Robj> for Struct
andTryFrom<&Robj> for Struct
on this struct.