Skip to main content

miniextendr_api/into_r/
large_integers.rs

1//! Large integer types → REALSXP (f64).
2//!
3//! R doesn't have native 64-bit integers. These types convert to f64 (REALSXP)
4//! which may lose precision for values outside the "safe integer" range.
5//!
6//! # Tradeoff
7//!
8//! These are the **lax** outbound impls — values in `(i32::MIN, i32::MAX]`
9//! become `INTSXP` and everything else silently widens to `REALSXP`, with
10//! precision loss above `2^53`. The strict alternative is
11//! [`crate::strict::checked_into_sexp_i64`] (and the `u64`/`isize`/`usize`
12//! siblings), reached via `#[miniextendr(strict)]` — those panic (→ R error)
13//! instead of widening. Failure mode of staying on the lax path with
14//! IDs/counters: collisions once values exceed `2^53` (≈ `9 × 10^15`).
15//!
16//! For storage-directed conversions (force `INTSXP`, error if out of range)
17//! see [`crate::into_r_as::IntoRAs`].
18
19use crate::altrep_traits::{NA_INTEGER, NA_LOGICAL, NA_REAL};
20use crate::into_r::IntoR;
21//
22// **Precision Loss Warning:**
23// - f64 can exactly represent integers in range [-2^53, 2^53] (±9,007,199,254,740,992)
24// - Values outside this range may be rounded to the nearest representable f64
25// - This is silent - no error or warning is raised
26//
27// **Alternatives for exact 64-bit integers:**
28// - Use the `bit64` R package (stores as REALSXP but interprets as int64)
29// - Store as character strings and parse in R
30// - Split into high/low 32-bit parts
31//
32// For most use cases (counters, IDs, timestamps), values fit within 2^53.
33
34/// Convert `i64` to R integer (INTSXP) or numeric (REALSXP).
35///
36/// Uses smart conversion: values in `(i32::MIN, i32::MAX]` are returned as
37/// R integers for exact representation. Values outside that range (including
38/// `i32::MIN` which is `NA_integer_` in R) fall back to R doubles.
39///
40/// ```ignore
41/// let small: i64 = 42;
42/// small.into_sexp(); // R integer 42L
43///
44/// let big: i64 = 3_000_000_000;
45/// big.into_sexp(); // R double 3e9
46///
47/// let na_trap: i64 = i32::MIN as i64;
48/// na_trap.into_sexp(); // R double (not NA_integer_!)
49/// ```
50impl IntoR for i64 {
51    type Error = std::convert::Infallible;
52    #[inline]
53    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
54        Ok(self.into_sexp())
55    }
56    #[inline]
57    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
58        Ok(unsafe { self.into_sexp_unchecked() })
59    }
60    #[inline]
61    fn into_sexp(self) -> crate::SEXP {
62        // i32::MIN is NA_integer_ in R, so exclude it from the integer range
63        if self > i32::MIN as i64 && self <= i32::MAX as i64 {
64            // Range guard verified — cast is safe
65            (self as i32).into_sexp()
66        } else {
67            // R has no 64-bit integer; f64 loses precision > 2^53
68            (self as f64).into_sexp()
69        }
70    }
71    #[inline]
72    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
73        if self > i32::MIN as i64 && self <= i32::MAX as i64 {
74            unsafe { (self as i32).into_sexp_unchecked() }
75        } else {
76            unsafe { (self as f64).into_sexp_unchecked() }
77        }
78    }
79}
80
81/// Convert `u64` to R integer (INTSXP) or numeric (REALSXP).
82///
83/// Values in `[0, i32::MAX]` are returned as R integers. Larger values
84/// fall back to R doubles (which may lose precision above 2^53).
85impl IntoR for u64 {
86    type Error = std::convert::Infallible;
87    #[inline]
88    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
89        Ok(self.into_sexp())
90    }
91    #[inline]
92    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
93        Ok(unsafe { self.into_sexp_unchecked() })
94    }
95    #[inline]
96    fn into_sexp(self) -> crate::SEXP {
97        if self <= i32::MAX as u64 {
98            (self as i32).into_sexp()
99        } else {
100            (self as f64).into_sexp()
101        }
102    }
103    #[inline]
104    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
105        if self <= i32::MAX as u64 {
106            unsafe { (self as i32).into_sexp_unchecked() }
107        } else {
108            unsafe { (self as f64).into_sexp_unchecked() }
109        }
110    }
111}
112
113/// Convert `isize` to R integer (INTSXP) or numeric (REALSXP).
114///
115/// On 64-bit platforms, uses the same smart conversion as [`i64`](impl IntoR for i64).
116/// On 32-bit platforms, `isize` fits in i32 so conversion is always exact.
117impl IntoR for isize {
118    type Error = std::convert::Infallible;
119    #[inline]
120    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
121        Ok((self as i64).into_sexp())
122    }
123    #[inline]
124    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
125        Ok(unsafe { self.into_sexp_unchecked() })
126    }
127    #[inline]
128    fn into_sexp(self) -> crate::SEXP {
129        (self as i64).into_sexp()
130    }
131    #[inline]
132    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
133        unsafe { (self as i64).into_sexp_unchecked() }
134    }
135}
136
137/// Convert `usize` to R integer (INTSXP) or numeric (REALSXP).
138///
139/// Values in `[0, i32::MAX]` are returned as R integers. Larger values
140/// fall back to R doubles.
141impl IntoR for usize {
142    type Error = std::convert::Infallible;
143    #[inline]
144    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
145        Ok((self as u64).into_sexp())
146    }
147    #[inline]
148    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
149        Ok(unsafe { self.into_sexp_unchecked() })
150    }
151    #[inline]
152    fn into_sexp(self) -> crate::SEXP {
153        (self as u64).into_sexp()
154    }
155    #[inline]
156    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
157        unsafe { (self as u64).into_sexp_unchecked() }
158    }
159}
160
161/// Macro for logical IntoR via Rf_ScalarLogical with conversion to i32.
162macro_rules! impl_logical_into_r {
163    ($ty:ty, $to_i32:expr) => {
164        impl IntoR for $ty {
165            type Error = std::convert::Infallible;
166            #[inline]
167            fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
168                Ok(crate::SEXP::scalar_logical_raw($to_i32(self)))
169            }
170            #[inline]
171            unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
172                Ok(unsafe { self.into_sexp_unchecked() })
173            }
174            #[inline]
175            fn into_sexp(self) -> crate::SEXP {
176                crate::SEXP::scalar_logical_raw($to_i32(self))
177            }
178            #[inline]
179            unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
180                unsafe { crate::SEXP::scalar_logical_raw_unchecked($to_i32(self)) }
181            }
182        }
183    };
184}
185
186impl IntoR for bool {
    type Error = std::convert::Infallible;
    #[inline]
    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
        Ok(crate::SEXP::scalar_logical_raw((|v: bool| i32::from(v))(self)))
    }
    #[inline]
    unsafe fn try_into_sexp_unchecked(self)
        -> Result<crate::SEXP, Self::Error> {
        Ok(unsafe { self.into_sexp_unchecked() })
    }
    #[inline]
    fn into_sexp(self) -> crate::SEXP {
        crate::SEXP::scalar_logical_raw((|v: bool| i32::from(v))(self))
    }
    #[inline]
    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
        unsafe {
            crate::SEXP::scalar_logical_raw_unchecked((|v: bool|
                        i32::from(v))(self))
        }
    }
}impl_logical_into_r!(bool, |v: bool| i32::from(v));
187impl IntoR for crate::Rboolean {
    type Error = std::convert::Infallible;
    #[inline]
    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
        Ok(crate::SEXP::scalar_logical_raw((|v: crate::Rboolean|
                        v as i32)(self)))
    }
    #[inline]
    unsafe fn try_into_sexp_unchecked(self)
        -> Result<crate::SEXP, Self::Error> {
        Ok(unsafe { self.into_sexp_unchecked() })
    }
    #[inline]
    fn into_sexp(self) -> crate::SEXP {
        crate::SEXP::scalar_logical_raw((|v: crate::Rboolean| v as i32)(self))
    }
    #[inline]
    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
        unsafe {
            crate::SEXP::scalar_logical_raw_unchecked((|v: crate::Rboolean|
                        v as i32)(self))
        }
    }
}impl_logical_into_r!(crate::Rboolean, |v: crate::Rboolean| v as i32);
188impl IntoR for crate::RLogical {
    type Error = std::convert::Infallible;
    #[inline]
    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
        Ok(crate::SEXP::scalar_logical_raw(crate::RLogical::to_i32(self)))
    }
    #[inline]
    unsafe fn try_into_sexp_unchecked(self)
        -> Result<crate::SEXP, Self::Error> {
        Ok(unsafe { self.into_sexp_unchecked() })
    }
    #[inline]
    fn into_sexp(self) -> crate::SEXP {
        crate::SEXP::scalar_logical_raw(crate::RLogical::to_i32(self))
    }
    #[inline]
    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
        unsafe {
            crate::SEXP::scalar_logical_raw_unchecked(crate::RLogical::to_i32(self))
        }
    }
}impl_logical_into_r!(crate::RLogical, crate::RLogical::to_i32);
189
190impl IntoR for Option<i32> {
191    type Error = std::convert::Infallible;
192    #[inline]
193    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
194        Ok(self.into_sexp())
195    }
196    #[inline]
197    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
198        Ok(unsafe { self.into_sexp_unchecked() })
199    }
200    #[inline]
201    fn into_sexp(self) -> crate::SEXP {
202        match self {
203            Some(v) => v.into_sexp(),
204            None => crate::SEXP::scalar_integer(NA_INTEGER),
205        }
206    }
207    #[inline]
208    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
209        match self {
210            Some(v) => unsafe { v.into_sexp_unchecked() },
211            None => unsafe { crate::SEXP::scalar_integer_unchecked(NA_INTEGER) },
212        }
213    }
214}
215
216impl IntoR for Option<f64> {
217    type Error = std::convert::Infallible;
218    #[inline]
219    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
220        Ok(self.into_sexp())
221    }
222    #[inline]
223    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
224        Ok(unsafe { self.into_sexp_unchecked() })
225    }
226    #[inline]
227    fn into_sexp(self) -> crate::SEXP {
228        match self {
229            Some(v) => v.into_sexp(),
230            None => crate::SEXP::scalar_real(NA_REAL),
231        }
232    }
233    #[inline]
234    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
235        match self {
236            Some(v) => unsafe { v.into_sexp_unchecked() },
237            None => unsafe { crate::SEXP::scalar_real_unchecked(NA_REAL) },
238        }
239    }
240}
241
242impl IntoR for Option<crate::Rboolean> {
243    type Error = std::convert::Infallible;
244    #[inline]
245    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
246        Ok(self.into_sexp())
247    }
248    #[inline]
249    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
250        Ok(unsafe { self.into_sexp_unchecked() })
251    }
252    #[inline]
253    fn into_sexp(self) -> crate::SEXP {
254        match self {
255            // Rboolean is repr(i32), `as i32` is a no-op transmute
256            Some(v) => crate::SEXP::scalar_logical_raw(v as i32),
257            None => crate::SEXP::scalar_logical_raw(NA_LOGICAL),
258        }
259    }
260    #[inline]
261    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
262        match self {
263            Some(v) => unsafe { crate::SEXP::scalar_logical_raw_unchecked(v as i32) },
264            None => unsafe { crate::SEXP::scalar_logical_raw_unchecked(NA_LOGICAL) },
265        }
266    }
267}
268
269impl IntoR for Option<crate::RLogical> {
270    type Error = std::convert::Infallible;
271    #[inline]
272    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
273        Ok(self.into_sexp())
274    }
275    #[inline]
276    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
277        Ok(unsafe { self.into_sexp_unchecked() })
278    }
279    #[inline]
280    fn into_sexp(self) -> crate::SEXP {
281        match self {
282            Some(v) => crate::SEXP::scalar_logical_raw(v.to_i32()),
283            None => crate::SEXP::scalar_logical_raw(NA_LOGICAL),
284        }
285    }
286    #[inline]
287    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
288        match self {
289            Some(v) => unsafe { crate::SEXP::scalar_logical_raw_unchecked(v.to_i32()) },
290            None => unsafe { crate::SEXP::scalar_logical_raw_unchecked(NA_LOGICAL) },
291        }
292    }
293}
294
295impl IntoR for Option<bool> {
296    type Error = std::convert::Infallible;
297    #[inline]
298    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
299        Ok(self.into_sexp())
300    }
301    #[inline]
302    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
303        Ok(unsafe { self.into_sexp_unchecked() })
304    }
305    #[inline]
306    fn into_sexp(self) -> crate::SEXP {
307        match self {
308            Some(v) => v.into_sexp(),
309            None => crate::SEXP::scalar_logical_raw(NA_LOGICAL),
310        }
311    }
312    #[inline]
313    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
314        match self {
315            Some(v) => unsafe { v.into_sexp_unchecked() },
316            None => unsafe { crate::SEXP::scalar_logical_raw_unchecked(NA_LOGICAL) },
317        }
318    }
319}
320
321/// Macro for NA-aware `Option<T> → R` smart scalar conversion.
322/// Checks if value fits i32 → INTSXP with NA_INTEGER for None,
323/// otherwise REALSXP with NA_REAL for None.
324macro_rules! impl_option_smart_i64_into_r {
325    ($t:ty, $fits_i32:expr) => {
326        impl IntoR for Option<$t> {
327            type Error = std::convert::Infallible;
328            #[inline]
329            fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
330                Ok(self.into_sexp())
331            }
332            #[inline]
333            unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
334                self.try_into_sexp()
335            }
336            #[inline]
337            fn into_sexp(self) -> crate::SEXP {
338                match self {
339                    Some(x) if $fits_i32(x) => (x as i32).into_sexp(),
340                    Some(x) => (x as f64).into_sexp(),
341                    None => crate::SEXP::scalar_integer(NA_INTEGER),
342                }
343            }
344        }
345    };
346}
347
348impl IntoR for Option<i64> {
    type Error = std::convert::Infallible;
    #[inline]
    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
        Ok(self.into_sexp())
    }
    #[inline]
    unsafe fn try_into_sexp_unchecked(self)
        -> Result<crate::SEXP, Self::Error> {
        self.try_into_sexp()
    }
    #[inline]
    fn into_sexp(self) -> crate::SEXP {
        match self {
            Some(x) if
                (|x: i64| x > i32::MIN as i64 && x <= i32::MAX as i64)(x) =>
                (x as i32).into_sexp(),
            Some(x) => (x as f64).into_sexp(),
            None => crate::SEXP::scalar_integer(NA_INTEGER),
        }
    }
}impl_option_smart_i64_into_r!(i64, |x: i64| x > i32::MIN as i64 && x <= i32::MAX as i64);
349impl IntoR for Option<u64> {
    type Error = std::convert::Infallible;
    #[inline]
    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
        Ok(self.into_sexp())
    }
    #[inline]
    unsafe fn try_into_sexp_unchecked(self)
        -> Result<crate::SEXP, Self::Error> {
        self.try_into_sexp()
    }
    #[inline]
    fn into_sexp(self) -> crate::SEXP {
        match self {
            Some(x) if (|x: u64| x <= i32::MAX as u64)(x) =>
                (x as i32).into_sexp(),
            Some(x) => (x as f64).into_sexp(),
            None => crate::SEXP::scalar_integer(NA_INTEGER),
        }
    }
}impl_option_smart_i64_into_r!(u64, |x: u64| x <= i32::MAX as u64);
350impl IntoR for Option<isize> {
    type Error = std::convert::Infallible;
    #[inline]
    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
        Ok(self.into_sexp())
    }
    #[inline]
    unsafe fn try_into_sexp_unchecked(self)
        -> Result<crate::SEXP, Self::Error> {
        self.try_into_sexp()
    }
    #[inline]
    fn into_sexp(self) -> crate::SEXP {
        match self {
            Some(x) if
                (|x: isize|
                        x > i32::MIN as isize && x <= i32::MAX as isize)(x) =>
                (x as i32).into_sexp(),
            Some(x) => (x as f64).into_sexp(),
            None => crate::SEXP::scalar_integer(NA_INTEGER),
        }
    }
}impl_option_smart_i64_into_r!(isize, |x: isize| x > i32::MIN as isize
351    && x <= i32::MAX as isize);
352impl IntoR for Option<usize> {
    type Error = std::convert::Infallible;
    #[inline]
    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
        Ok(self.into_sexp())
    }
    #[inline]
    unsafe fn try_into_sexp_unchecked(self)
        -> Result<crate::SEXP, Self::Error> {
        self.try_into_sexp()
    }
    #[inline]
    fn into_sexp(self) -> crate::SEXP {
        match self {
            Some(x) if (|x: usize| x <= i32::MAX as usize)(x) =>
                (x as i32).into_sexp(),
            Some(x) => (x as f64).into_sexp(),
            None => crate::SEXP::scalar_integer(NA_INTEGER),
        }
    }
}impl_option_smart_i64_into_r!(usize, |x: usize| x <= i32::MAX as usize);
353
354/// Macro for `Option<T>` where `T` coerces to a type with existing Option impl.
355macro_rules! impl_option_coerce_into_r {
356    ($from:ty => $to:ty) => {
357        impl IntoR for Option<$from> {
358            type Error = std::convert::Infallible;
359            #[inline]
360            fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
361                Ok(self.map(|x| x as $to).into_sexp())
362            }
363            #[inline]
364            unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
365                self.try_into_sexp()
366            }
367            #[inline]
368            fn into_sexp(self) -> crate::SEXP {
369                self.map(|x| x as $to).into_sexp()
370            }
371        }
372    };
373}
374
375impl IntoR for Option<i8> {
    type Error = std::convert::Infallible;
    #[inline]
    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
        Ok(self.map(|x| x as i32).into_sexp())
    }
    #[inline]
    unsafe fn try_into_sexp_unchecked(self)
        -> Result<crate::SEXP, Self::Error> {
        self.try_into_sexp()
    }
    #[inline]
    fn into_sexp(self) -> crate::SEXP { self.map(|x| x as i32).into_sexp() }
}impl_option_coerce_into_r!(i8 => i32);
376impl IntoR for Option<i16> {
    type Error = std::convert::Infallible;
    #[inline]
    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
        Ok(self.map(|x| x as i32).into_sexp())
    }
    #[inline]
    unsafe fn try_into_sexp_unchecked(self)
        -> Result<crate::SEXP, Self::Error> {
        self.try_into_sexp()
    }
    #[inline]
    fn into_sexp(self) -> crate::SEXP { self.map(|x| x as i32).into_sexp() }
}impl_option_coerce_into_r!(i16 => i32);
377impl IntoR for Option<u16> {
    type Error = std::convert::Infallible;
    #[inline]
    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
        Ok(self.map(|x| x as i32).into_sexp())
    }
    #[inline]
    unsafe fn try_into_sexp_unchecked(self)
        -> Result<crate::SEXP, Self::Error> {
        self.try_into_sexp()
    }
    #[inline]
    fn into_sexp(self) -> crate::SEXP { self.map(|x| x as i32).into_sexp() }
}impl_option_coerce_into_r!(u16 => i32);
378impl IntoR for Option<u32> {
    type Error = std::convert::Infallible;
    #[inline]
    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
        Ok(self.map(|x| x as i64).into_sexp())
    }
    #[inline]
    unsafe fn try_into_sexp_unchecked(self)
        -> Result<crate::SEXP, Self::Error> {
        self.try_into_sexp()
    }
    #[inline]
    fn into_sexp(self) -> crate::SEXP { self.map(|x| x as i64).into_sexp() }
}impl_option_coerce_into_r!(u32 => i64); // delegates to smart i64 path
379impl IntoR for Option<f32> {
    type Error = std::convert::Infallible;
    #[inline]
    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
        Ok(self.map(|x| x as f64).into_sexp())
    }
    #[inline]
    unsafe fn try_into_sexp_unchecked(self)
        -> Result<crate::SEXP, Self::Error> {
        self.try_into_sexp()
    }
    #[inline]
    fn into_sexp(self) -> crate::SEXP { self.map(|x| x as f64).into_sexp() }
}impl_option_coerce_into_r!(f32 => f64);
380
381impl<T: crate::externalptr::TypedExternal> IntoR for crate::externalptr::ExternalPtr<T> {
382    type Error = std::convert::Infallible;
383    #[inline]
384    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
385        Ok(self.as_sexp())
386    }
387    #[inline]
388    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
389        Ok(self.as_sexp())
390    }
391    #[inline]
392    fn into_sexp(self) -> crate::SEXP {
393        self.as_sexp()
394    }
395}
396
397/// Convert `Option<ExternalPtr<T>>` to R: `Some` → the external pointer,
398/// `None` → NULL, as a `Vec<Option<ExternalPtr<T>>>` element converts.
399impl<T: crate::externalptr::TypedExternal> IntoR for Option<crate::externalptr::ExternalPtr<T>> {
400    type Error = std::convert::Infallible;
401    #[inline]
402    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
403        Ok(self.into_sexp())
404    }
405    #[inline]
406    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
407        Ok(self.into_sexp())
408    }
409    #[inline]
410    fn into_sexp(self) -> crate::SEXP {
411        match self {
412            Some(ptr) => ptr.as_sexp(),
413            None => crate::SEXP::nil(),
414        }
415    }
416}
417
418/// Build an R list (VECSXP) of external pointers from `Vec<ExternalPtr<T>>`.
419///
420/// Each element `EXTPTRSXP` is rooted in the process-wide
421/// [`ProtectPool`](crate::protect_pool) for the lifetime of its `ExternalPtr`
422/// handle (#836/#841), so holding them in a `Vec` is GC-safe and laying them
423/// into a freshly allocated list needs no extra protection — see
424/// [`vec_externalptr_to_list`].
425impl<T: crate::externalptr::TypedExternal> IntoR for Vec<crate::externalptr::ExternalPtr<T>> {
426    type Error = std::convert::Infallible;
427    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
428        Ok(self.into_sexp())
429    }
430    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
431        self.try_into_sexp()
432    }
433    fn into_sexp(self) -> crate::SEXP {
434        vec_externalptr_to_list(self)
435    }
436}
437
438/// Build an R list (VECSXP) from `Vec<Option<ExternalPtr<T>>>`, with `None`
439/// mapping to `NULL` and `Some(ptr)` to the external pointer (issue #827).
440impl<T: crate::externalptr::TypedExternal> IntoR
441    for Vec<Option<crate::externalptr::ExternalPtr<T>>>
442{
443    type Error = std::convert::Infallible;
444    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
445        Ok(self.into_sexp())
446    }
447    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
448        self.try_into_sexp()
449    }
450    fn into_sexp(self) -> crate::SEXP {
451        vec_option_externalptr_to_list(self)
452    }
453}
454
455/// Allocate a VECSXP and populate it with the external pointers in `items`.
456///
457/// Each element is rooted in the process-wide [`ProtectPool`] for as long as its
458/// `ExternalPtr` handle lives, and `items` owns those handles across the
459/// `Rf_allocVector` below — so allocating the list cannot collect them and no
460/// pre-protection is required. Storing into the freshly allocated list never
461/// allocates, so the list itself stays live until it becomes the `.Call`
462/// result, which R protects. (Before #841 the handles were unprotected, which
463/// forced a pre-protect of every element here.)
464///
465/// This receives *already-built* handles, so it can't use the faster
466/// [`ExternalPtr::collect_into_r_list`] — that builds fresh `EXTPTRSXP`s from
467/// owned `T` values and is the path to prefer when you start from a `Vec<T>`
468/// rather than a `Vec<ExternalPtr<T>>`.
469///
470/// [`ProtectPool`]: crate::protect_pool::ProtectPool
471/// [`ExternalPtr::collect_into_r_list`]: crate::externalptr::ExternalPtr::collect_into_r_list
472fn vec_externalptr_to_list<T: crate::externalptr::TypedExternal>(
473    items: Vec<crate::externalptr::ExternalPtr<T>>,
474) -> crate::SEXP {
475    use crate::SexpExt;
476    // SAFETY: return-value conversion runs on R's main thread (after
477    // `run_on_worker` hands the `Vec` back); every element is pool-rooted and
478    // kept alive by `items` across the single allocation.
479    unsafe {
480        let n = items.len();
481        let list = crate::sys::Rf_allocVector(crate::SEXPTYPE::VECSXP, n as crate::R_xlen_t);
482        for (i, item) in items.iter().enumerate() {
483            list.set_vector_elt(i as crate::R_xlen_t, item.as_sexp());
484        }
485        list
486    }
487}
488
489/// As [`vec_externalptr_to_list`], but `None` elements become `NULL`.
490fn vec_option_externalptr_to_list<T: crate::externalptr::TypedExternal>(
491    items: Vec<Option<crate::externalptr::ExternalPtr<T>>>,
492) -> crate::SEXP {
493    use crate::SexpExt;
494    // SAFETY: see `vec_externalptr_to_list` — each `Some` handle is pool-rooted
495    // and kept alive by `items` across the allocation; `None` becomes `NULL`.
496    unsafe {
497        let n = items.len();
498        let list = crate::sys::Rf_allocVector(crate::SEXPTYPE::VECSXP, n as crate::R_xlen_t);
499        for (i, item) in items.iter().enumerate() {
500            let elt = match item {
501                Some(ext) => ext.as_sexp(),
502                None => crate::SEXP::nil(),
503            };
504            list.set_vector_elt(i as crate::R_xlen_t, elt);
505        }
506        list
507    }
508}
509
510/// Blanket impl: Types marked with `IntoExternalPtr` get automatic `IntoR`.
511///
512/// This wraps the value in `ExternalPtr<T>` automatically, so you can return
513/// `MyType` directly from `#[miniextendr]` functions instead of `ExternalPtr<MyType>`.
514impl<T: crate::externalptr::IntoExternalPtr> IntoR for T {
515    type Error = std::convert::Infallible;
516    #[inline]
517    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
518        Ok(self.into_sexp())
519    }
520    #[inline]
521    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
522        Ok(unsafe { self.into_sexp_unchecked() })
523    }
524    #[inline]
525    fn into_sexp(self) -> crate::SEXP {
526        crate::externalptr::ExternalPtr::new(self).into_sexp()
527    }
528    #[inline]
529    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
530        unsafe { crate::externalptr::ExternalPtr::new_unchecked(self).into_sexp() }
531    }
532}
533
534/// Helper to convert a string slice to R CHARSXP.
535/// Uses UTF-8 encoding. Empty strings return R_BlankString (static, no allocation).
536#[inline]
537pub(crate) fn str_to_charsxp(s: &str) -> crate::SEXP {
538    if s.is_empty() {
539        crate::SEXP::blank_string()
540    } else {
541        let _len: i32 = s.len().try_into().expect("string exceeds i32::MAX bytes");
542        crate::SEXP::charsxp(s)
543    }
544}
545
546/// Unchecked version of [`str_to_charsxp`].
547#[inline]
548pub(crate) unsafe fn str_to_charsxp_unchecked(s: &str) -> crate::SEXP {
549    unsafe {
550        if s.is_empty() {
551            crate::SEXP::blank_string()
552        } else {
553            let len: i32 = s.len().try_into().expect("string exceeds i32::MAX bytes");
554            crate::sys::Rf_mkCharLenCE_unchecked(s.as_ptr().cast(), len, crate::sexp_types::CE_UTF8)
555        }
556    }
557}
558
559impl IntoR for String {
560    type Error = crate::into_r_error::IntoRError;
561    #[inline]
562    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
563        self.as_str().try_into_sexp()
564    }
565    #[inline]
566    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
567        Ok(unsafe { self.into_sexp_unchecked() })
568    }
569    #[inline]
570    fn into_sexp(self) -> crate::SEXP {
571        self.as_str().into_sexp()
572    }
573    #[inline]
574    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
575        unsafe { self.as_str().into_sexp_unchecked() }
576    }
577}
578
579impl IntoR for Box<str> {
580    type Error = crate::into_r_error::IntoRError;
581    #[inline]
582    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
583        // `String::from(Box<str>)` is O(1) (reuses the allocation); the produced
584        // SEXP is identical to the `String` impl's — a `character(1)` STRSXP.
585        String::from(self).try_into_sexp()
586    }
587    #[inline]
588    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
589        Ok(unsafe { self.into_sexp_unchecked() })
590    }
591    #[inline]
592    fn into_sexp(self) -> crate::SEXP {
593        String::from(self).into_sexp()
594    }
595    #[inline]
596    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
597        unsafe { String::from(self).into_sexp_unchecked() }
598    }
599}
600
601impl IntoR for char {
602    type Error = std::convert::Infallible;
603    #[inline]
604    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
605        Ok(self.into_sexp())
606    }
607    #[inline]
608    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
609        Ok(unsafe { self.into_sexp_unchecked() })
610    }
611    #[inline]
612    fn into_sexp(self) -> crate::SEXP {
613        // Convert char to a single-character string — always ≤ 4 bytes, cannot overflow i32
614        let mut buf = [0u8; 4];
615        let s = self.encode_utf8(&mut buf);
616        crate::SEXP::scalar_string(str_to_charsxp(s))
617    }
618    #[inline]
619    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
620        let mut buf = [0u8; 4];
621        let s = self.encode_utf8(&mut buf);
622        unsafe { s.into_sexp_unchecked() }
623    }
624}
625
626impl IntoR for &str {
627    type Error = crate::into_r_error::IntoRError;
628    #[inline]
629    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
630        let _len = i32::try_from(self.len())
631            .map_err(|_| crate::into_r_error::IntoRError::StringTooLong { len: self.len() })?;
632        Ok(crate::SEXP::scalar_string(str_to_charsxp(self)))
633    }
634    #[inline]
635    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
636        Ok(unsafe { self.into_sexp_unchecked() })
637    }
638    #[inline]
639    fn into_sexp(self) -> crate::SEXP {
640        crate::SEXP::scalar_string(str_to_charsxp(self))
641    }
642    #[inline]
643    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
644        unsafe {
645            let charsxp = str_to_charsxp_unchecked(self);
646            crate::SEXP::scalar_string_unchecked(charsxp)
647        }
648    }
649}
650
651/// Convert `Option<&str>` to R: `Some(s)` → character, `None` → `NA_character_`.
652///
653/// This is a deliberate **exception** to the generic `Option<&T> where T:
654/// Copy` blanket impl below (which returns `NULL` for `None`, not NA).
655/// `str` is unsized, so it can't satisfy that impl's `Copy` bound —
656/// `&str` gets its own hand-written impl here instead, and it's written to
657/// mirror `Option<String>`'s NA semantics rather than the reference-type
658/// NULL semantics. See the module-level "absence contract" doc on
659/// [`crate::into_r`] for the full return-category table.
660impl IntoR for Option<&str> {
661    type Error = crate::into_r_error::IntoRError;
662    #[inline]
663    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
664        match self {
665            Some(s) => {
666                let _len = i32::try_from(s.len())
667                    .map_err(|_| crate::into_r_error::IntoRError::StringTooLong { len: s.len() })?;
668                Ok(crate::SEXP::scalar_string(str_to_charsxp(s)))
669            }
670            None => Ok(crate::SEXP::scalar_string(crate::SEXP::na_string())),
671        }
672    }
673    #[inline]
674    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
675        Ok(unsafe { self.into_sexp_unchecked() })
676    }
677    #[inline]
678    fn into_sexp(self) -> crate::SEXP {
679        let charsxp = match self {
680            Some(s) => str_to_charsxp(s),
681            None => crate::SEXP::na_string(),
682        };
683        crate::SEXP::scalar_string(charsxp)
684    }
685    #[inline]
686    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
687        unsafe {
688            let charsxp = match self {
689                Some(s) => str_to_charsxp_unchecked(s),
690                None => crate::SEXP::na_string(),
691            };
692            crate::SEXP::scalar_string_unchecked(charsxp)
693        }
694    }
695}
696
697impl IntoR for Option<String> {
698    type Error = crate::into_r_error::IntoRError;
699    #[inline]
700    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
701        self.as_deref().try_into_sexp()
702    }
703    #[inline]
704    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
705        Ok(unsafe { self.into_sexp_unchecked() })
706    }
707    #[inline]
708    fn into_sexp(self) -> crate::SEXP {
709        self.as_deref().into_sexp()
710    }
711    #[inline]
712    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
713        unsafe { self.as_deref().into_sexp_unchecked() }
714    }
715}
716
717/// Convert `Option<&T>` to R SEXP by copying the value.
718///
719/// - `Some(&v)` → copies `v` and converts to R
720/// - `None` → returns `NULL` (R_NilValue)
721///
722/// Note: This returns NULL for None, not NA, since there's no reference to return.
723/// Use `Option<T>` directly if you want NA semantics for scalar types.
724///
725/// `T: Copy` excludes `str` (unsized), so `Option<&str>` does **not** go
726/// through this impl — see the dedicated `Option<&str>` impl above, which
727/// returns `NA_character_` instead of `NULL`.
728impl<T> IntoR for Option<&T>
729where
730    T: Copy + IntoR,
731{
732    type Error = crate::into_r_error::IntoRError;
733    #[inline]
734    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
735        match self {
736            Some(&v) => v
737                .try_into_sexp()
738                .map_err(|e| crate::into_r_error::IntoRError::Inner(e.to_string())),
739            None => Ok(crate::SEXP::nil()),
740        }
741    }
742    #[inline]
743    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
744        Ok(unsafe { self.into_sexp_unchecked() })
745    }
746    #[inline]
747    fn into_sexp(self) -> crate::SEXP {
748        match self {
749            Some(&v) => v.into_sexp(),
750            None => crate::SEXP::nil(),
751        }
752    }
753    #[inline]
754    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
755        match self {
756            Some(&v) => unsafe { v.into_sexp_unchecked() },
757            None => crate::SEXP::nil(),
758        }
759    }
760}
761
762// endregion