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}