Skip to main content

miniextendr_api/
error_value.rs

1//! Tagged error value transport for `#[miniextendr(error_in_r)]` mode.
2//!
3//! When `error_in_r` is enabled, Rust-origin failures (panics, `Result::Err`,
4//! `Option::None`) are converted to a tagged SEXP value instead of raising an
5//! R error immediately. The generated R wrapper inspects this tagged value and
6//! escalates it to a proper R error condition past the Rust boundary.
7//!
8//! This ensures Rust destructors run cleanly before R sees the error.
9//!
10//! # Error value structure
11//!
12//! The tagged SEXP is a named list with:
13//! - `error`: error message (character scalar)
14//! - `kind`: error kind string (`"panic"`, `"result_err"`, `"none_err"`)
15//! - `call`: the R call SEXP (or `NULL` if not available)
16//! - class attribute: `"rust_error_value"`
17//! - `__rust_error__` attribute: `TRUE`
18
19use crate::cached_class::{error_names_sexp, rust_error_attr_symbol, rust_error_class_sexp};
20use crate::ffi::{self, SEXP, SexpExt};
21
22/// Build a tagged error-value SEXP for transport across the Rust→R boundary.
23///
24/// # Safety
25///
26/// Must be called from R's main thread (standard R API constraint).
27/// The returned SEXP is unprotected — caller must protect if needed.
28///
29/// # Arguments
30///
31/// * `message` - Human-readable error message
32/// * `kind` - Machine-readable error kind: `"panic"`, `"result_err"`, `"none_err"`,
33///   or `"other_rust_error"`
34/// * `call` - Optional R call SEXP for error context. When `None`, uses `R_NilValue`.
35pub fn make_rust_error_value(message: &str, kind: &str, call: Option<SEXP>) -> SEXP {
36    unsafe {
37        // Allocate a list of length 3: (error, kind, call)
38        let list = ffi::Rf_allocVector(ffi::SEXPTYPE::VECSXP, 3);
39        ffi::Rf_protect(list);
40
41        // Set list element 0: error message
42        let msg_cstr = std::ffi::CString::new(message)
43            .unwrap_or_else(|_| std::ffi::CString::new("<invalid error message>").unwrap());
44        let msg_charsxp = ffi::Rf_mkCharCE(msg_cstr.as_ptr(), ffi::CE_UTF8);
45        list.set_vector_elt(0, SEXP::scalar_string(msg_charsxp));
46
47        // Set list element 1: kind string
48        let kind_cstr = std::ffi::CString::new(kind)
49            .unwrap_or_else(|_| std::ffi::CString::new("other_rust_error").unwrap());
50        let kind_charsxp = ffi::Rf_mkCharCE(kind_cstr.as_ptr(), ffi::CE_UTF8);
51        list.set_vector_elt(1, SEXP::scalar_string(kind_charsxp));
52
53        // Set list element 2: call SEXP
54        let call_sexp = call.unwrap_or(SEXP::nil());
55        list.set_vector_elt(2, call_sexp);
56
57        // Names, class, and attribute symbol are all cached — zero allocation
58        list.set_names(error_names_sexp());
59        list.set_class(rust_error_class_sexp());
60        list.set_attr(rust_error_attr_symbol(), SEXP::scalar_logical(true));
61
62        ffi::Rf_unprotect(1);
63        list
64    }
65}