Expression Evaluation Helpers
Safe wrappers for building and evaluating R function calls from Rust.
Safe wrappers for building and evaluating R function calls from Rust.
πTypes
| Type | Purpose |
|---|---|
RSymbol | Interned R symbol (SYMSXP) β never GCβd |
RCall | Builder for R function calls (LANGSXP) |
REnv | Well-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)
unsafeblocks (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_helpersmodule usesRCallinternally - 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
- CLASS_SYSTEMS.md β S4 helpers built on RCall
- THREADS.md β Main thread requirements
- GC_PROTECT.md β Protecting returned SEXPs