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_logical_into_r!(bool, |v: bool| i32::from(v));
187impl_logical_into_r!(crate::Rboolean, |v: crate::Rboolean| v as i32);
188impl_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_option_smart_i64_into_r!(i64, |x: i64| x > i32::MIN as i64 && x <= i32::MAX as i64);
349impl_option_smart_i64_into_r!(u64, |x: u64| x <= i32::MAX as u64);
350impl_option_smart_i64_into_r!(isize, |x: isize| x > i32::MIN as isize
351    && x <= i32::MAX as isize);
352impl_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_option_coerce_into_r!(i8 => i32);
376impl_option_coerce_into_r!(i16 => i32);
377impl_option_coerce_into_r!(u16 => i32);
378impl_option_coerce_into_r!(u32 => i64); // delegates to smart i64 path
379impl_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/// Build an R list (VECSXP) of external pointers from `Vec<ExternalPtr<T>>`.
398///
399/// Each element `EXTPTRSXP` is rooted in the process-wide
400/// [`ProtectPool`](crate::protect_pool) for the lifetime of its `ExternalPtr`
401/// handle (#836/#841), so holding them in a `Vec` is GC-safe and laying them
402/// into a freshly allocated list needs no extra protection — see
403/// [`vec_externalptr_to_list`].
404impl<T: crate::externalptr::TypedExternal> IntoR for Vec<crate::externalptr::ExternalPtr<T>> {
405    type Error = std::convert::Infallible;
406    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
407        Ok(self.into_sexp())
408    }
409    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
410        self.try_into_sexp()
411    }
412    fn into_sexp(self) -> crate::SEXP {
413        vec_externalptr_to_list(self)
414    }
415}
416
417/// Build an R list (VECSXP) from `Vec<Option<ExternalPtr<T>>>`, with `None`
418/// mapping to `NULL` and `Some(ptr)` to the external pointer (issue #827).
419impl<T: crate::externalptr::TypedExternal> IntoR
420    for Vec<Option<crate::externalptr::ExternalPtr<T>>>
421{
422    type Error = std::convert::Infallible;
423    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
424        Ok(self.into_sexp())
425    }
426    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
427        self.try_into_sexp()
428    }
429    fn into_sexp(self) -> crate::SEXP {
430        vec_option_externalptr_to_list(self)
431    }
432}
433
434/// Allocate a VECSXP and populate it with the external pointers in `items`.
435///
436/// Each element is rooted in the process-wide [`ProtectPool`] for as long as its
437/// `ExternalPtr` handle lives, and `items` owns those handles across the
438/// `Rf_allocVector` below — so allocating the list cannot collect them and no
439/// pre-protection is required. Storing into the freshly allocated list never
440/// allocates, so the list itself stays live until it becomes the `.Call`
441/// result, which R protects. (Before #841 the handles were unprotected, which
442/// forced a pre-protect of every element here.)
443///
444/// This receives *already-built* handles, so it can't use the faster
445/// [`ExternalPtr::collect_into_r_list`] — that builds fresh `EXTPTRSXP`s from
446/// owned `T` values and is the path to prefer when you start from a `Vec<T>`
447/// rather than a `Vec<ExternalPtr<T>>`.
448///
449/// [`ProtectPool`]: crate::protect_pool::ProtectPool
450/// [`ExternalPtr::collect_into_r_list`]: crate::externalptr::ExternalPtr::collect_into_r_list
451fn vec_externalptr_to_list<T: crate::externalptr::TypedExternal>(
452    items: Vec<crate::externalptr::ExternalPtr<T>>,
453) -> crate::SEXP {
454    use crate::SexpExt;
455    // SAFETY: return-value conversion runs on R's main thread (after
456    // `run_on_worker` hands the `Vec` back); every element is pool-rooted and
457    // kept alive by `items` across the single allocation.
458    unsafe {
459        let n = items.len();
460        let list = crate::sys::Rf_allocVector(crate::SEXPTYPE::VECSXP, n as crate::R_xlen_t);
461        for (i, item) in items.iter().enumerate() {
462            list.set_vector_elt(i as crate::R_xlen_t, item.as_sexp());
463        }
464        list
465    }
466}
467
468/// As [`vec_externalptr_to_list`], but `None` elements become `NULL`.
469fn vec_option_externalptr_to_list<T: crate::externalptr::TypedExternal>(
470    items: Vec<Option<crate::externalptr::ExternalPtr<T>>>,
471) -> crate::SEXP {
472    use crate::SexpExt;
473    // SAFETY: see `vec_externalptr_to_list` — each `Some` handle is pool-rooted
474    // and kept alive by `items` across the allocation; `None` becomes `NULL`.
475    unsafe {
476        let n = items.len();
477        let list = crate::sys::Rf_allocVector(crate::SEXPTYPE::VECSXP, n as crate::R_xlen_t);
478        for (i, item) in items.iter().enumerate() {
479            let elt = match item {
480                Some(ext) => ext.as_sexp(),
481                None => crate::SEXP::nil(),
482            };
483            list.set_vector_elt(i as crate::R_xlen_t, elt);
484        }
485        list
486    }
487}
488
489/// Blanket impl: Types marked with `IntoExternalPtr` get automatic `IntoR`.
490///
491/// This wraps the value in `ExternalPtr<T>` automatically, so you can return
492/// `MyType` directly from `#[miniextendr]` functions instead of `ExternalPtr<MyType>`.
493impl<T: crate::externalptr::IntoExternalPtr> IntoR for T {
494    type Error = std::convert::Infallible;
495    #[inline]
496    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
497        Ok(self.into_sexp())
498    }
499    #[inline]
500    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
501        Ok(unsafe { self.into_sexp_unchecked() })
502    }
503    #[inline]
504    fn into_sexp(self) -> crate::SEXP {
505        crate::externalptr::ExternalPtr::new(self).into_sexp()
506    }
507    #[inline]
508    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
509        unsafe { crate::externalptr::ExternalPtr::new_unchecked(self).into_sexp() }
510    }
511}
512
513/// Helper to convert a string slice to R CHARSXP.
514/// Uses UTF-8 encoding. Empty strings return R_BlankString (static, no allocation).
515#[inline]
516pub(crate) fn str_to_charsxp(s: &str) -> crate::SEXP {
517    if s.is_empty() {
518        crate::SEXP::blank_string()
519    } else {
520        let _len: i32 = s.len().try_into().expect("string exceeds i32::MAX bytes");
521        crate::SEXP::charsxp(s)
522    }
523}
524
525/// Unchecked version of [`str_to_charsxp`].
526#[inline]
527pub(crate) unsafe fn str_to_charsxp_unchecked(s: &str) -> crate::SEXP {
528    unsafe {
529        if s.is_empty() {
530            crate::SEXP::blank_string()
531        } else {
532            let len: i32 = s.len().try_into().expect("string exceeds i32::MAX bytes");
533            crate::sys::Rf_mkCharLenCE_unchecked(s.as_ptr().cast(), len, crate::sexp_types::CE_UTF8)
534        }
535    }
536}
537
538impl IntoR for String {
539    type Error = crate::into_r_error::IntoRError;
540    #[inline]
541    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
542        self.as_str().try_into_sexp()
543    }
544    #[inline]
545    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
546        Ok(unsafe { self.into_sexp_unchecked() })
547    }
548    #[inline]
549    fn into_sexp(self) -> crate::SEXP {
550        self.as_str().into_sexp()
551    }
552    #[inline]
553    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
554        unsafe { self.as_str().into_sexp_unchecked() }
555    }
556}
557
558impl IntoR for Box<str> {
559    type Error = crate::into_r_error::IntoRError;
560    #[inline]
561    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
562        // `String::from(Box<str>)` is O(1) (reuses the allocation); the produced
563        // SEXP is identical to the `String` impl's — a `character(1)` STRSXP.
564        String::from(self).try_into_sexp()
565    }
566    #[inline]
567    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
568        Ok(unsafe { self.into_sexp_unchecked() })
569    }
570    #[inline]
571    fn into_sexp(self) -> crate::SEXP {
572        String::from(self).into_sexp()
573    }
574    #[inline]
575    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
576        unsafe { String::from(self).into_sexp_unchecked() }
577    }
578}
579
580impl IntoR for char {
581    type Error = std::convert::Infallible;
582    #[inline]
583    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
584        Ok(self.into_sexp())
585    }
586    #[inline]
587    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
588        Ok(unsafe { self.into_sexp_unchecked() })
589    }
590    #[inline]
591    fn into_sexp(self) -> crate::SEXP {
592        // Convert char to a single-character string — always ≤ 4 bytes, cannot overflow i32
593        let mut buf = [0u8; 4];
594        let s = self.encode_utf8(&mut buf);
595        crate::SEXP::scalar_string(str_to_charsxp(s))
596    }
597    #[inline]
598    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
599        let mut buf = [0u8; 4];
600        let s = self.encode_utf8(&mut buf);
601        unsafe { s.into_sexp_unchecked() }
602    }
603}
604
605impl IntoR for &str {
606    type Error = crate::into_r_error::IntoRError;
607    #[inline]
608    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
609        let _len = i32::try_from(self.len())
610            .map_err(|_| crate::into_r_error::IntoRError::StringTooLong { len: self.len() })?;
611        Ok(crate::SEXP::scalar_string(str_to_charsxp(self)))
612    }
613    #[inline]
614    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
615        Ok(unsafe { self.into_sexp_unchecked() })
616    }
617    #[inline]
618    fn into_sexp(self) -> crate::SEXP {
619        crate::SEXP::scalar_string(str_to_charsxp(self))
620    }
621    #[inline]
622    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
623        unsafe {
624            let charsxp = str_to_charsxp_unchecked(self);
625            crate::SEXP::scalar_string_unchecked(charsxp)
626        }
627    }
628}
629
630/// Convert `Option<&str>` to R: `Some(s)` → character, `None` → `NA_character_`.
631///
632/// This is a deliberate **exception** to the generic `Option<&T> where T:
633/// Copy` blanket impl below (which returns `NULL` for `None`, not NA).
634/// `str` is unsized, so it can't satisfy that impl's `Copy` bound —
635/// `&str` gets its own hand-written impl here instead, and it's written to
636/// mirror `Option<String>`'s NA semantics rather than the reference-type
637/// NULL semantics. See the module-level "absence contract" doc on
638/// [`crate::into_r`] for the full return-category table.
639impl IntoR for Option<&str> {
640    type Error = crate::into_r_error::IntoRError;
641    #[inline]
642    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
643        match self {
644            Some(s) => {
645                let _len = i32::try_from(s.len())
646                    .map_err(|_| crate::into_r_error::IntoRError::StringTooLong { len: s.len() })?;
647                Ok(crate::SEXP::scalar_string(str_to_charsxp(s)))
648            }
649            None => Ok(crate::SEXP::scalar_string(crate::SEXP::na_string())),
650        }
651    }
652    #[inline]
653    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
654        Ok(unsafe { self.into_sexp_unchecked() })
655    }
656    #[inline]
657    fn into_sexp(self) -> crate::SEXP {
658        let charsxp = match self {
659            Some(s) => str_to_charsxp(s),
660            None => crate::SEXP::na_string(),
661        };
662        crate::SEXP::scalar_string(charsxp)
663    }
664    #[inline]
665    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
666        unsafe {
667            let charsxp = match self {
668                Some(s) => str_to_charsxp_unchecked(s),
669                None => crate::SEXP::na_string(),
670            };
671            crate::SEXP::scalar_string_unchecked(charsxp)
672        }
673    }
674}
675
676impl IntoR for Option<String> {
677    type Error = crate::into_r_error::IntoRError;
678    #[inline]
679    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
680        self.as_deref().try_into_sexp()
681    }
682    #[inline]
683    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
684        Ok(unsafe { self.into_sexp_unchecked() })
685    }
686    #[inline]
687    fn into_sexp(self) -> crate::SEXP {
688        self.as_deref().into_sexp()
689    }
690    #[inline]
691    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
692        unsafe { self.as_deref().into_sexp_unchecked() }
693    }
694}
695
696/// Convert `Option<&T>` to R SEXP by copying the value.
697///
698/// - `Some(&v)` → copies `v` and converts to R
699/// - `None` → returns `NULL` (R_NilValue)
700///
701/// Note: This returns NULL for None, not NA, since there's no reference to return.
702/// Use `Option<T>` directly if you want NA semantics for scalar types.
703///
704/// `T: Copy` excludes `str` (unsized), so `Option<&str>` does **not** go
705/// through this impl — see the dedicated `Option<&str>` impl above, which
706/// returns `NA_character_` instead of `NULL`.
707impl<T> IntoR for Option<&T>
708where
709    T: Copy + IntoR,
710{
711    type Error = crate::into_r_error::IntoRError;
712    #[inline]
713    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
714        match self {
715            Some(&v) => v
716                .try_into_sexp()
717                .map_err(|e| crate::into_r_error::IntoRError::Inner(e.to_string())),
718            None => Ok(crate::SEXP::nil()),
719        }
720    }
721    #[inline]
722    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
723        Ok(unsafe { self.into_sexp_unchecked() })
724    }
725    #[inline]
726    fn into_sexp(self) -> crate::SEXP {
727        match self {
728            Some(&v) => v.into_sexp(),
729            None => crate::SEXP::nil(),
730        }
731    }
732    #[inline]
733    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
734        match self {
735            Some(&v) => unsafe { v.into_sexp_unchecked() },
736            None => crate::SEXP::nil(),
737        }
738    }
739}
740
741// endregion