Safe wrappers for building and evaluating R function calls from Rust.

πŸ”—Types

TypePurpose
RSymbolInterned R symbol (SYMSXP) – never GC’d
RCallBuilder for R function calls (LANGSXP)
REnvWell-known R environments (Global, Base, Empty)

Plus free functions: r_eval_str / r_eval_str_global (parse + evaluate a string of R source) and dollar_extract (the R $ operator).

πŸ”—Quick Example

use miniextendr_api::expression::{RCall, REnv};
use miniextendr_api::sys::Rf_mkString;

unsafe {
    // Call paste0("hello", " world") in base
    let result = RCall::new("paste0")
        .arg(Rf_mkString(c"hello".as_ptr()))
        .arg(Rf_mkString(c" world".as_ptr()))
        .eval(REnv::base().as_sexp())?;
}

πŸ”—RSymbol

Wraps R’s Rf_install() for interned symbols. Symbols are never garbage collected, so RSymbol needs no GC protection.

use miniextendr_api::expression::RSymbol;

// From a Rust string (allocates a CString)
let sym = unsafe { RSymbol::new("my_var") };

// From a C string literal (zero allocation)
let sym = unsafe { RSymbol::from_cstr(c"my_var") };

// Use as SEXP
let sexp = sym.as_sexp();

πŸ”—RCall

Builds R function calls with positional and named arguments.

use miniextendr_api::expression::RCall;

unsafe {
    // Positional arguments
    let result = RCall::new("sum")
        .arg(my_vector_sexp)
        .eval(env)?;

    // Named arguments
    let result = RCall::new("paste")
        .arg(x_sexp)
        .arg(y_sexp)
        .named_arg("sep", sep_sexp)
        .eval(env)?;
}

πŸ”—Error Handling

eval() uses R_tryEvalSilent and returns Result<SEXP, String>. On failure, the error message comes from R’s geterrmessage().

match RCall::new("stop").arg(msg_sexp).eval(env) {
    Ok(result) => { /* success */ },
    Err(msg) => { /* msg contains R's error message */ },
}

πŸ”—GC Protection

RCall protects all intermediate SEXPs (the call object and argument list) during construction. The returned SEXP is unprotected – caller must protect it if it will survive across R API calls.

πŸ”—REnv

Provides handles to R’s well-known environments:

use miniextendr_api::expression::REnv;

unsafe {
    let global = REnv::global();                     // R_GlobalEnv
    let base = REnv::base();                         // R_BaseEnv
    let empty = REnv::empty();                       // R_EmptyEnv
    let base_ns = REnv::base_namespace();            // R_BaseNamespace (for .Internal etc.)
    let methods = REnv::package_namespace("methods")?; // getNamespace("methods")
    let caller = REnv::caller();                     // calling environment (R_GetCurrentEnv)

    // Use as SEXP
    let sexp = base.as_sexp();
}

Prefer package_namespace(pkg) over chasing symbols through R_GlobalEnv. The former mirrors getNamespace(pkg) and resolves against the package’s own namespace regardless of what the user has attached on the search path. eval_global() has been removed; evaluate in base(), base_namespace(), or the caller’s env instead.

πŸ”—r_eval_str

Parse a string of R source and evaluate it β€” the runtime workhorse behind the r_str! / r! macros. Every top-level expression is evaluated in order (so side effects take effect); the value of the last one is returned, matching eval(parse(text = ...)). Empty / whitespace-only input yields R_NilValue.

use miniextendr_api::expression::{r_eval_str, r_eval_str_global};

unsafe {
    // In a specific environment
    let three = r_eval_str("1L + 2L", env)?;

    // Convenience wrapper for R_GlobalEnv
    let six = r_eval_str_global("local({ x <- 2; x * 3 })")?;
}

Parse failures (syntax error, incomplete input) and R evaluation errors both come back as Err(String) β€” evaluation uses R_tryEvalSilent, so R errors never longjmp through Rust frames. The returned SEXP is unprotected.

πŸ”—dollar_extract

Convenience wrapper for the R $ extraction operator, replacing hand-rolled Rf_install("$") + Rf_lang3 + R_tryEvalSilent ladders:

use miniextendr_api::expression::dollar_extract;

unsafe {
    let value = dollar_extract(list_sexp, "field_name")?;
}

πŸ”—Safety Requirements

All functions in this module require:

  • Being called from the R main thread (they use R API calls)
  • unsafe blocks (they call into C)

Standalone #[miniextendr] functions already run on the main thread (they are the default), so these calls are safe there; they are also safe within ALTREP callbacks, which run on the main thread too.

πŸ”—Use Cases

  • S4 slot access: The s4_helpers module uses RCall internally
  • Calling R functions from ALTREP callbacks: When elt() needs to call R
  • Dynamic dispatch: Building R function calls based on runtime data
  • Package interop: Calling functions from other R packages

πŸ”—See Also