Skip to main content

miniextendr_api/
error.rs

1//! Error handling helpers for R API calls.
2//!
3//! **In `#[miniextendr]` functions, use `panic!()` instead of `r_stop`.**
4//! Panics are caught by `catch_unwind` and propagated cleanly as R errors.
5//!
6//! `r_stop` calls `Rf_error` (longjmp). It is used internally by:
7//! - The proc-macro generated argument validation / return type unwrapping
8//! - The `CatchUnwind` guard (after catch_unwind has caught a panic)
9//! - Trait ABI shims
10//!
11//! These are all inside `R_UnwindProtect` or after `catch_unwind`, where
12//! `Rf_error` longjmp is safe. User code should never call `r_stop` directly.
13//!
14//! # Example
15//!
16//! ```ignore
17//! use miniextendr_api::miniextendr;
18//!
19//! #[miniextendr]
20//! fn validate_input(x: i32) -> i32 {
21//!     assert!(x >= 0, "x must be non-negative, got {x}");
22//!     x * 2
23//! }
24//! ```
25
26/// Raise an R error via `Rf_error` (longjmp). **Do not call from user code** —
27/// use `panic!()` instead, which is caught by the framework.
28///
29/// This is used internally by generated code and the FFI guard layer.
30///
31/// # Panics
32///
33/// Panics if the message contains null bytes.
34#[inline]
35pub fn r_stop(msg: &str) -> ! {
36    let c_msg = std::ffi::CString::new(msg).expect("r_stop: message contains null bytes");
37
38    if crate::worker::is_r_main_thread() {
39        unsafe {
40            crate::ffi::Rf_error_unchecked(c"%s".as_ptr(), c_msg.as_ptr());
41        }
42    } else {
43        // Route to main thread
44        crate::worker::with_r_thread(move || unsafe {
45            crate::ffi::Rf_error_unchecked(c"%s".as_ptr(), c_msg.as_ptr());
46        })
47    }
48}
49
50/// Raise an R warning with the given message.
51///
52/// Unlike `r_stop`, this returns normally after issuing the warning.
53/// Automatically routes to R's main thread if called from a worker thread.
54#[inline]
55pub fn r_warning(msg: &str) {
56    let c_msg = std::ffi::CString::new(msg).expect("r_warning: message contains null bytes");
57
58    if crate::worker::is_r_main_thread() {
59        unsafe {
60            crate::ffi::Rf_warning_unchecked(c"%s".as_ptr(), c_msg.as_ptr());
61        }
62    } else {
63        crate::worker::with_r_thread(move || unsafe {
64            crate::ffi::Rf_warning_unchecked(c"%s".as_ptr(), c_msg.as_ptr());
65        });
66    }
67}
68
69/// Print a message to R's console (internal implementation).
70/// Automatically routes to R's main thread if called from a worker thread.
71#[doc(hidden)]
72#[inline]
73pub fn _r_print_str(msg: &str) {
74    let c_msg = std::ffi::CString::new(msg).expect("r_print!: message contains null bytes");
75
76    if crate::worker::is_r_main_thread() {
77        unsafe {
78            crate::ffi::Rprintf_unchecked(c"%s".as_ptr(), c_msg.as_ptr());
79        }
80    } else {
81        crate::worker::with_r_thread(move || unsafe {
82            crate::ffi::Rprintf_unchecked(c"%s".as_ptr(), c_msg.as_ptr());
83        });
84    }
85}
86
87/// Print a newline to R's console (internal implementation).
88/// Automatically routes to R's main thread if called from a worker thread.
89#[doc(hidden)]
90#[inline]
91pub fn _r_print_newline() {
92    if crate::worker::is_r_main_thread() {
93        unsafe {
94            crate::ffi::Rprintf_unchecked(c"\n".as_ptr());
95        }
96    } else {
97        crate::worker::with_r_thread(|| unsafe {
98            crate::ffi::Rprintf_unchecked(c"\n".as_ptr());
99        });
100    }
101}
102
103/// Print to R's console (like `print!`).
104///
105/// # Example
106///
107/// ```ignore
108/// use miniextendr_api::r_print;
109///
110/// r_print!("Hello ");
111/// r_print!("value: {}", 42);
112/// ```
113#[macro_export]
114macro_rules! r_print {
115    () => {};
116    ($($arg:tt)*) => {
117        $crate::error::_r_print_str(&format!($($arg)*))
118    };
119}
120
121/// Print to R's console with a newline (like `println!`).
122///
123/// # Example
124///
125/// ```ignore
126/// use miniextendr_api::r_println;
127///
128/// r_println!();  // just a newline
129/// r_println!("Hello, world!");
130/// r_println!("value: {}", 42);
131/// ```
132#[macro_export]
133macro_rules! r_println {
134    () => {
135        $crate::error::_r_print_newline()
136    };
137    ($($arg:tt)*) => {{
138        $crate::error::_r_print_str(&format!($($arg)*));
139        $crate::error::_r_print_newline();
140    }};
141}