Skip to main content

miniextendr_api/into_r/
result.rs

1//! `Result<T, E>` conversions to R.
2//!
3//! Used by `#[miniextendr(unwrap_in_r)]` to pass Results to R as lists
4//! with `ok` and `err` fields, instead of unwrapping in Rust.
5//! Also provides `NullOnErr` for `Result<T, ()>` → NULL-on-error semantics.
6
7use std::collections::HashMap;
8
9use crate::into_r::IntoR;
10
11/// Convert `Result<T, E>` to R (value-style, for `#[miniextendr(unwrap_in_r)]`).
12///
13/// # Behavior
14///
15/// - `Ok(value)` → returns the converted value directly
16/// - `Err(msg)` → returns `list(error = "<msg>")` (value-style error)
17///
18/// # When This Is Used
19///
20/// This impl is **only used** when `#[miniextendr(unwrap_in_r)]` is specified.
21/// Without that attribute, `#[miniextendr]` functions returning `Result<T, E>`
22/// will unwrap in Rust and raise an R error on `Err` (error boundary semantics).
23///
24/// # Error Handling Summary
25///
26/// | Mode | On `Err(e)` | Bound Required |
27/// |------|-------------|----------------|
28/// | Default | R error via panic | `E: Debug` |
29/// | `unwrap_in_r` | `list(error = ...)` | `E: Display` |
30///
31/// **Default** (without `unwrap_in_r`): `Result<T, E>` acts as an error boundary:
32/// - `Ok(v)` → `v` converted to R
33/// - `Err(e)` → R error with Debug-formatted message (requires `E: Debug`)
34///
35/// **With `unwrap_in_r`**: `Result<T, E>` is passed through to R:
36/// - `Ok(v)` → `v` converted to R
37/// - `Err(e)` → `list(error = e.to_string())` (requires `E: Display`)
38///
39/// # Example
40///
41/// ```ignore
42/// // Default: error boundary - Err becomes R stop()
43/// #[miniextendr]
44/// fn divide(x: f64, y: f64) -> Result<f64, String> {
45///     if y == 0.0 { Err("division by zero".into()) }
46///     else { Ok(x / y) }
47/// }
48/// // In R: tryCatch(divide(1, 0), error = ...) catches the error
49///
50/// // Value-style: Err becomes list(error = ...)
51/// #[miniextendr(unwrap_in_r)]
52/// fn divide_safe(x: f64, y: f64) -> Result<f64, String> {
53///     if y == 0.0 { Err("division by zero".into()) }
54///     else { Ok(x / y) }
55/// }
56/// // In R: result <- divide_safe(1, 0)
57/// //       if (!is.null(result$error)) { handle error }
58/// ```
59impl<T, E> IntoR for Result<T, E>
60where
61    T: IntoR,
62    E: std::fmt::Display,
63{
64    type Error = std::convert::Infallible;
65    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
66        Ok(self.into_sexp())
67    }
68    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
69        self.try_into_sexp()
70    }
71    fn into_sexp(self) -> crate::ffi::SEXP {
72        match self {
73            Ok(value) => value.into_sexp(),
74            Err(msg) => {
75                // Create list(error = msg) for R-side error handling
76                let mut map = HashMap::with_capacity(1);
77                map.insert("error".to_string(), msg.to_string());
78                map.into_sexp()
79            }
80        }
81    }
82}
83
84/// Marker type for `Result<T, ()>` that converts `Err(())` to NULL.
85///
86/// This type is used internally by the `#[miniextendr]` macro when handling
87/// `Result<T, ()>` return types. When the error type is `()`, there's no
88/// error message to report, so we return NULL instead of raising an error.
89///
90/// # Usage
91///
92/// You typically don't use this directly. When you write:
93///
94/// ```ignore
95/// #[miniextendr]
96/// fn maybe_value(x: i32) -> Result<i32, ()> {
97///     if x > 0 { Ok(x) } else { Err(()) }
98/// }
99/// ```
100///
101/// The macro generates code that converts `Err(())` to `Err(NullOnErr)` and
102/// returns `NULL` in R.
103///
104/// # Note
105///
106/// `NullOnErr` intentionally does NOT implement `Display` to avoid conflicting
107/// with the generic `IntoR for Result<T, E: Display>` impl. It has its own
108/// specialized `IntoR` impl that returns NULL on error.
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub struct NullOnErr;
111
112/// Convert `Result<T, NullOnErr>` to R, returning NULL on error.
113///
114/// This is a special case for `Result<T, ()>` types where the error
115/// carries no information. Instead of raising an R error, we return NULL.
116impl<T: IntoR> IntoR for Result<T, NullOnErr> {
117    type Error = std::convert::Infallible;
118    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
119        Ok(self.into_sexp())
120    }
121    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
122        self.try_into_sexp()
123    }
124    fn into_sexp(self) -> crate::ffi::SEXP {
125        match self {
126            Ok(value) => value.into_sexp(),
127            Err(NullOnErr) => crate::ffi::SEXP::nil(),
128        }
129    }
130}
131// endregion