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}