Skip to main content

miniextendr_api/
convert.rs

1//! Wrapper helpers to force specific `IntoR` representations.
2//!
3//! This module provides two approaches for controlling how Rust types are converted to R:
4//!
5//! ## 1. `As*` Wrappers (Call-site Control)
6//!
7//! Use these wrappers when you want to override the conversion for a single return value:
8//!
9//! - [`AsList<T>`]: Convert `T` to an R list via [`IntoList`]
10//! - [`AsExternalPtr<T>`]: Convert `T` to an R external pointer
11//! - [`AsRNative<T>`]: Convert scalar `T` to a length-1 R vector
12//!
13//! ```ignore
14//! #[miniextendr]
15//! fn get_data() -> AsList<MyStruct> {
16//!     AsList(MyStruct { x: 1, y: 2 })
17//! }
18//! ```
19//!
20//! ## 2. `Prefer*` Derive Macros (Type-level Control)
21//!
22//! Use these derives when a type should *always* use a specific conversion:
23//!
24//! - `#[derive(IntoList, PreferList)]`: Type always converts to R list
25//! - `#[derive(ExternalPtr, PreferExternalPtr)]`: Type always converts to external pointer
26//! - `#[derive(RNativeType, PreferRNativeType)]`: Newtype always converts to native R scalar
27//!
28//! ```ignore
29//! #[derive(IntoList, PreferList)]
30//! struct Point { x: f64, y: f64 }
31//!
32//! #[miniextendr]
33//! fn make_point() -> Point {  // Automatically becomes R list
34//!     Point { x: 1.0, y: 2.0 }
35//! }
36//! ```
37//!
38//! ## 3. `#[miniextendr(return = "...")]` Attribute
39//!
40//! Use this when you want to control conversion for a specific `#[miniextendr]` function
41//! without modifying the type itself:
42//!
43//! - `return = "list"`: Wrap result in `AsList`
44//! - `return = "externalptr"`: Wrap result in `AsExternalPtr`
45//! - `return = "native"`: Wrap result in `AsRNative`
46//!
47//! ```ignore
48//! #[miniextendr(return = "list")]
49//! fn get_as_list() -> MyStruct {
50//!     MyStruct { x: 1 }
51//! }
52//! ```
53//!
54//! ## Choosing the Right Approach
55//!
56//! | Situation | Recommended Approach |
57//! |-----------|---------------------|
58//! | Type should *always* convert one way | `Prefer*` derive |
59//! | Override conversion for one function | `As*` wrapper or `return` attribute |
60//! | Type has multiple valid representations | Don't use `Prefer*`; use `As*` or `return` |
61
62use crate::externalptr::{ExternalPtr, IntoExternalPtr};
63use crate::ffi::{RNativeType, SexpExt};
64use crate::into_r::IntoR;
65use crate::list::{IntoList, List};
66use crate::named_vector::AtomicElement;
67
68/// Wrap a value and convert it to an R list via [`IntoList`] when returned from Rust.
69///
70/// Use this wrapper when you want to convert a single value to an R list without
71/// making that the default behavior for the type.
72///
73/// # Example
74///
75/// ```ignore
76/// #[derive(IntoList)]
77/// struct Point { x: f64, y: f64 }
78///
79/// #[miniextendr]
80/// fn make_point() -> AsList<Point> {
81///     AsList(Point { x: 1.0, y: 2.0 })
82/// }
83/// // In R: make_point() returns list(x = 1.0, y = 2.0)
84/// ```
85#[derive(Debug, Clone, Copy)]
86pub struct AsList<T: IntoList>(pub T);
87
88impl<T: IntoList> From<T> for AsList<T> {
89    fn from(value: T) -> Self {
90        AsList(value)
91    }
92}
93
94impl<T: IntoList> IntoR for AsList<T> {
95    type Error = std::convert::Infallible;
96
97    #[inline]
98    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
99        Ok(self.into_sexp())
100    }
101
102    #[inline]
103    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
104        self.try_into_sexp()
105    }
106
107    #[inline]
108    fn into_sexp(self) -> crate::ffi::SEXP {
109        self.0.into_list().into_sexp()
110    }
111}
112
113/// Wrap a value and convert it to an R data.frame via [`IntoDataFrame`] when returned from Rust.
114///
115/// Use this wrapper when you want to convert a single value to an R data.frame without
116/// making that the default behavior for the type.
117///
118/// # Example
119///
120/// ```ignore
121/// struct TimeSeries {
122///     timestamps: Vec<f64>,
123///     values: Vec<f64>,
124/// }
125///
126/// impl IntoDataFrame for TimeSeries {
127///     fn into_data_frame(self) -> List {
128///         List::from_pairs(vec![
129///             ("timestamp", self.timestamps),
130///             ("value", self.values),
131///         ])
132///         .set_class_str(&["data.frame"])
133///         .set_row_names_int(self.timestamps.len())
134///     }
135/// }
136///
137/// #[miniextendr]
138/// fn make_time_series() -> ToDataFrame<TimeSeries> {
139///     ToDataFrame(TimeSeries {
140///         timestamps: vec![1.0, 2.0, 3.0],
141///         values: vec![10.0, 20.0, 30.0],
142///     })
143/// }
144/// // In R: make_time_series() returns a data.frame
145/// ```
146#[derive(Debug, Clone, Copy)]
147pub struct ToDataFrame<T: IntoDataFrame>(pub T);
148
149impl<T: IntoDataFrame> From<T> for ToDataFrame<T> {
150    fn from(value: T) -> Self {
151        ToDataFrame(value)
152    }
153}
154
155impl<T: IntoDataFrame> IntoR for ToDataFrame<T> {
156    type Error = std::convert::Infallible;
157
158    #[inline]
159    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
160        Ok(self.into_sexp())
161    }
162
163    #[inline]
164    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
165        self.try_into_sexp()
166    }
167
168    #[inline]
169    fn into_sexp(self) -> crate::ffi::SEXP {
170        self.0.into_data_frame().into_sexp()
171    }
172}
173
174/// IntoR implementation for DataFrame.
175///
176/// This allows DataFrame to be returned directly from `#[miniextendr]` functions.
177impl<T: IntoList> IntoR for DataFrame<T> {
178    type Error = std::convert::Infallible;
179
180    #[inline]
181    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
182        Ok(self.into_sexp())
183    }
184
185    #[inline]
186    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
187        self.try_into_sexp()
188    }
189
190    #[inline]
191    fn into_sexp(self) -> crate::ffi::SEXP {
192        self.into_data_frame().into_sexp()
193    }
194}
195
196/// Trait for types that can be converted into R data frames.
197///
198/// This trait allows Rust types to define how they convert to R data frames.
199/// Use with [`ToDataFrame`] wrapper or `#[derive(PreferDataFrame)]` to enable
200/// automatic conversion.
201///
202/// # Example
203///
204/// ```ignore
205/// use miniextendr_api::convert::IntoDataFrame;
206/// use miniextendr_api::List;
207///
208/// struct TimeSeries {
209///     timestamps: Vec<f64>,
210///     values: Vec<f64>,
211/// }
212///
213/// impl IntoDataFrame for TimeSeries {
214///     fn into_data_frame(self) -> List {
215///         List::from_pairs(vec![
216///             ("timestamp", self.timestamps),
217///             ("value", self.values),
218///         ])
219///         .set_class_str(&["data.frame"])
220///         .set_row_names_int(self.timestamps.len())
221///     }
222/// }
223/// ```
224///
225/// # Comparison with `AsDataFrame` coercion trait
226///
227/// - [`AsDataFrame`](crate::as_coerce::AsDataFrame): Used with `#[miniextendr(as = "data.frame")]`
228///   to generate S3 methods for `as.data.frame()` on external pointer types
229/// - `IntoDataFrame`: Used for direct conversion when returning from functions
230///
231/// Both return a `List` with appropriate data.frame attributes, but serve different purposes:
232/// - S3 `AsDataFrame` is for coercion methods on existing objects (`&self`)
233/// - `IntoDataFrame` is for consuming conversion (`self`) when returning from functions
234pub trait IntoDataFrame {
235    /// Convert this value into an R data.frame.
236    ///
237    /// The returned List should have:
238    /// - Named columns of equal length
239    /// - Class attribute set to "data.frame"
240    /// - row.names attribute set appropriately
241    ///
242    /// # Example
243    ///
244    /// ```ignore
245    /// impl IntoDataFrame for MyStruct {
246    ///     fn into_data_frame(self) -> List {
247    ///         List::from_pairs(vec![
248    ///             ("col1", self.field1),
249    ///             ("col2", self.field2),
250    ///         ])
251    ///         .set_class_str(&["data.frame"])
252    ///         .set_row_names_int(self.field1.len())
253    ///     }
254    /// }
255    /// ```
256    fn into_data_frame(self) -> List;
257}
258
259// region: Serde Row Wrapper
260
261/// Wrap a serde-serializable value for use as a data frame row.
262///
263/// This wrapper implements [`IntoList`] via serde serialization, allowing
264/// types that implement `serde::Serialize` to be used with [`DataFrame`]
265/// without manually implementing [`IntoList`].
266///
267/// # Feature Flag
268///
269/// Requires the `serde` feature to be enabled.
270///
271/// # Example
272///
273/// ```ignore
274/// use miniextendr_api::{miniextendr, convert::{AsSerializeRow, DataFrame}};
275/// use serde::Serialize;
276///
277/// #[derive(Serialize)]
278/// struct Measurement {
279///     time: f64,
280///     value: f64,
281/// }
282///
283/// #[miniextendr]
284/// fn get_data() -> DataFrame<AsSerializeRow<Measurement>> {
285///     DataFrame::from_rows(vec![
286///         AsSerializeRow(Measurement { time: 1.0, value: 10.0 }),
287///         AsSerializeRow(Measurement { time: 2.0, value: 20.0 }),
288///     ])
289/// }
290/// ```
291#[cfg(feature = "serde")]
292#[derive(Debug, Clone, Copy)]
293pub struct AsSerializeRow<T: serde::Serialize>(pub T);
294
295#[cfg(feature = "serde")]
296impl<T: serde::Serialize> From<T> for AsSerializeRow<T> {
297    fn from(value: T) -> Self {
298        AsSerializeRow(value)
299    }
300}
301
302#[cfg(feature = "serde")]
303impl<T: serde::Serialize> IntoList for AsSerializeRow<T> {
304    fn into_list(self) -> List {
305        use crate::ffi::{SEXPTYPE, SexpExt};
306        use crate::serde::RSerializer;
307        match RSerializer::to_sexp(&self.0) {
308            Ok(sexp) => {
309                if sexp.type_of() == SEXPTYPE::VECSXP {
310                    unsafe { List::from_raw(sexp) }
311                } else {
312                    // Non-list SEXP (e.g., scalar) — wrap in a single-element list
313                    List::from_raw_values(vec![sexp])
314                }
315            }
316            Err(e) => {
317                panic!("AsSerializeRow: serde serialization failed: {e}");
318            }
319        }
320    }
321}
322
323/// Type alias for a [`DataFrame`] of serde-serializable rows.
324///
325/// This is equivalent to `DataFrame<AsSerializeRow<T>>` but more concise.
326///
327/// # Example
328///
329/// ```ignore
330/// use miniextendr_api::{miniextendr, SerializeDataFrame};
331/// use serde::Serialize;
332///
333/// #[derive(Serialize)]
334/// struct Person {
335///     name: String,
336///     age: i32,
337/// }
338///
339/// #[miniextendr]
340/// fn get_people() -> SerializeDataFrame<Person> {
341///     let people = vec![
342///         Person { name: "Alice".into(), age: 30 },
343///         Person { name: "Bob".into(), age: 25 },
344///     ];
345///     SerializeDataFrame::from_serialize(people)
346/// }
347/// ```
348#[cfg(feature = "serde")]
349pub type SerializeDataFrame<T> = DataFrame<AsSerializeRow<T>>;
350// endregion
351
352// region: Data Frame Row Conversion
353
354/// Convert row-oriented data into a column-oriented R data.frame.
355///
356/// This type collects a sequence of row elements (structs implementing [`IntoList`])
357/// and transposes them into column vectors suitable for creating an R data.frame.
358///
359/// # Example
360///
361/// ```ignore
362/// use miniextendr_api::{miniextendr, convert::DataFrame};
363///
364/// #[derive(IntoList)]
365/// struct Person {
366///     name: String,
367///     age: i32,
368///     height: f64,
369/// }
370///
371/// #[miniextendr]
372/// fn make_people() -> DataFrame<Person> {
373///     DataFrame::from_rows(vec![
374///         Person { name: "Alice".into(), age: 30, height: 165.0 },
375///         Person { name: "Bob".into(), age: 25, height: 180.0 },
376///         Person { name: "Carol".into(), age: 35, height: 170.0 },
377///     ])
378/// }
379/// // In R: make_people() returns a data.frame with 3 rows and columns: name, age, height
380/// ```
381///
382/// # Row-oriented to Column-oriented
383///
384/// R data frames are column-oriented (each column is a vector), but data is often
385/// produced row-by-row in Rust. `DataFrame` handles the transposition:
386///
387/// ```text
388/// Input (row-oriented):           Output (column-oriented):
389/// Row 1: {name: "A", age: 30}     name column:  ["A", "B", "C"]
390/// Row 2: {name: "B", age: 25}  →  age column:   [30, 25, 35]
391/// Row 3: {name: "C", age: 35}
392/// ```
393#[derive(Debug, Clone)]
394pub struct DataFrame<T: IntoList> {
395    rows: Vec<T>,
396}
397
398impl<T: IntoList> DataFrame<T> {
399    /// Create a new `DataFrame` from a vector of row elements.
400    pub fn from_rows(rows: Vec<T>) -> Self {
401        Self { rows }
402    }
403
404    /// Create an empty `DataFrame`.
405    pub fn new() -> Self {
406        Self { rows: Vec::new() }
407    }
408
409    /// Add a row to the data frame.
410    pub fn push(&mut self, row: T) {
411        self.rows.push(row);
412    }
413
414    /// Get the number of rows.
415    pub fn len(&self) -> usize {
416        self.rows.len()
417    }
418
419    /// Check if empty.
420    pub fn is_empty(&self) -> bool {
421        self.rows.is_empty()
422    }
423}
424
425#[cfg(feature = "serde")]
426impl<T: serde::Serialize> DataFrame<AsSerializeRow<T>> {
427    /// Create a DataFrame from serde-serializable rows.
428    ///
429    /// This is a convenience method that wraps each row in [`AsSerializeRow`]
430    /// automatically, avoiding the need to manually map over the input.
431    ///
432    /// # Example
433    ///
434    /// ```ignore
435    /// use miniextendr_api::DataFrame;
436    /// use serde::Serialize;
437    ///
438    /// #[derive(Serialize)]
439    /// struct Person { name: String, age: i32 }
440    ///
441    /// let people = vec![
442    ///     Person { name: "Alice".into(), age: 30 },
443    ///     Person { name: "Bob".into(), age: 25 },
444    /// ];
445    ///
446    /// // Instead of:
447    /// // DataFrame::from_iter(people.into_iter().map(AsSerializeRow))
448    ///
449    /// // Just write:
450    /// let df = DataFrame::from_serialize(people);
451    /// ```
452    pub fn from_serialize(rows: impl IntoIterator<Item = T>) -> Self {
453        Self::from_iter(rows.into_iter().map(AsSerializeRow))
454    }
455}
456
457impl<T: IntoList> Default for DataFrame<T> {
458    fn default() -> Self {
459        Self::new()
460    }
461}
462
463impl<T: IntoList> FromIterator<T> for DataFrame<T> {
464    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
465        Self {
466            rows: iter.into_iter().collect(),
467        }
468    }
469}
470
471impl<T: IntoList> IntoDataFrame for DataFrame<T> {
472    fn into_data_frame(self) -> List {
473        if self.rows.is_empty() {
474            // Empty data frame
475            return List::from_raw_pairs(Vec::<(&str, crate::ffi::SEXP)>::new())
476                .set_data_frame_class()
477                .set_row_names_int(0);
478        }
479
480        let mut n_protect: i32 = 0;
481
482        // Convert all rows to lists, protecting each from GC.
483        let lists: Vec<List> = self
484            .rows
485            .into_iter()
486            .map(|row| {
487                let list = row.into_list();
488                unsafe { crate::ffi::Rf_protect(list.as_sexp()) };
489                n_protect += 1;
490                list
491            })
492            .collect();
493        let n_rows = lists.len() as isize;
494
495        // Get column names from the first row
496        let first_names_sexp = lists[0].names();
497        if first_names_sexp.is_none() {
498            unsafe { crate::ffi::Rf_unprotect(n_protect) };
499            panic!("cannot create data frame from unnamed list elements");
500        }
501
502        // Extract column names as Vec<String>
503        let names_sexp = first_names_sexp.expect("checked is_none above");
504        let n_cols = unsafe { crate::ffi::Rf_xlength(names_sexp) };
505        let mut col_names = Vec::with_capacity(n_cols as usize);
506        for i in 0..n_cols {
507            unsafe {
508                let name_sexp = names_sexp.string_elt(i);
509                let name_ptr = name_sexp.r_char();
510                let name_cstr = std::ffi::CStr::from_ptr(name_ptr);
511                if let Ok(s) = name_cstr.to_str() {
512                    col_names.push(s.to_string());
513                }
514            }
515        }
516
517        // Transpose: collect values by column.
518        // Element SEXPs from get_named are children of protected row lists,
519        // so they don't need individual protection.
520        use std::collections::HashMap;
521        let mut columns: HashMap<String, Vec<crate::ffi::SEXP>> =
522            HashMap::with_capacity(col_names.len());
523        for name in &col_names {
524            columns.insert(name.clone(), Vec::with_capacity(n_rows as usize));
525        }
526
527        for list in &lists {
528            for name in &col_names {
529                let value = list
530                    .get_named::<crate::ffi::SEXP>(name)
531                    .unwrap_or(crate::ffi::SEXP::nil());
532                columns
533                    .get_mut(name)
534                    .expect("column inserted above")
535                    .push(value);
536            }
537        }
538
539        // Build column vectors, protecting each from GC.
540        // Coalesce homogeneous length-1 scalars into atomic vectors so that
541        // columns are INTSXP/REALSXP/LGLSXP/STRSXP instead of VECSXP (list).
542        let mut df_pairs: Vec<(String, crate::ffi::SEXP)> = Vec::with_capacity(col_names.len());
543        for name in col_names {
544            let col_values = columns.remove(&name).expect("column inserted above");
545            let col_sexp = List::from_scalars_or_list(&col_values).as_sexp();
546            unsafe { crate::ffi::Rf_protect(col_sexp) };
547            n_protect += 1;
548            df_pairs.push((name, col_sexp));
549        }
550
551        let result = List::from_raw_pairs(df_pairs)
552            .set_data_frame_class()
553            .set_row_names_int(n_rows as usize);
554        unsafe { crate::ffi::Rf_unprotect(n_protect) };
555        result
556    }
557}
558
559/// Wrap a value and convert it to an R external pointer when returned from Rust.
560///
561/// Use this wrapper when you want to return a Rust value as an opaque pointer
562/// that R code can pass back to Rust functions later.
563///
564/// # Example
565///
566/// ```ignore
567/// struct Connection { handle: u64 }
568///
569/// impl IntoExternalPtr for Connection { /* ... */ }
570///
571/// #[miniextendr]
572/// fn open_connection(path: &str) -> AsExternalPtr<Connection> {
573///     AsExternalPtr(Connection { handle: 42 })
574/// }
575/// // In R: open_connection("foo") returns an external pointer
576/// ```
577#[derive(Debug, Clone, Copy)]
578pub struct AsExternalPtr<T: IntoExternalPtr>(pub T);
579
580impl<T: IntoExternalPtr> From<T> for AsExternalPtr<T> {
581    fn from(value: T) -> Self {
582        AsExternalPtr(value)
583    }
584}
585
586impl<T: IntoExternalPtr> IntoR for AsExternalPtr<T> {
587    type Error = std::convert::Infallible;
588
589    #[inline]
590    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
591        Ok(self.into_sexp())
592    }
593
594    #[inline]
595    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
596        self.try_into_sexp()
597    }
598
599    #[inline]
600    fn into_sexp(self) -> crate::ffi::SEXP {
601        ExternalPtr::new(self.0).into_sexp()
602    }
603}
604
605/// Wrap a scalar [`RNativeType`] and force native R vector conversion.
606///
607/// This creates a length-1 R vector containing the scalar value. Use this when
608/// you want to ensure a value is converted to its native R representation (e.g.,
609/// `i32` → integer vector, `f64` → numeric vector) rather than another path
610/// like `IntoExternalPtr`.
611///
612/// # Example
613///
614/// ```ignore
615/// #[derive(Clone, Copy, RNativeType)]
616/// struct Meters(f64);
617///
618/// #[miniextendr]
619/// fn distance() -> AsRNative<Meters> {
620///     AsRNative(Meters(42.5))
621/// }
622/// // In R: distance() returns 42.5 (numeric vector of length 1)
623/// ```
624///
625/// # Performance
626///
627/// This wrapper directly allocates an R vector and writes the value,
628/// avoiding intermediate Rust allocations.
629#[derive(Debug, Clone, Copy)]
630pub struct AsRNative<T: RNativeType>(pub T);
631
632impl<T: RNativeType> From<T> for AsRNative<T> {
633    fn from(value: T) -> Self {
634        AsRNative(value)
635    }
636}
637
638impl<T: RNativeType> IntoR for AsRNative<T> {
639    type Error = std::convert::Infallible;
640
641    #[inline]
642    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
643        Ok(self.into_sexp())
644    }
645
646    #[inline]
647    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
648        Ok(unsafe { self.into_sexp_unchecked() })
649    }
650
651    #[inline]
652    fn into_sexp(self) -> crate::ffi::SEXP {
653        // Directly allocate a length-1 R vector and write the scalar value.
654        // This avoids the intermediate Rust Vec allocation.
655        unsafe {
656            let sexp = crate::ffi::Rf_allocVector(T::SEXP_TYPE, 1);
657            let ptr = T::dataptr_mut(sexp);
658            std::ptr::write(ptr, self.0);
659            sexp
660        }
661    }
662
663    #[inline]
664    unsafe fn into_sexp_unchecked(self) -> crate::ffi::SEXP {
665        unsafe {
666            let sexp = crate::ffi::Rf_allocVector_unchecked(T::SEXP_TYPE, 1);
667            let ptr = T::dataptr_mut(sexp);
668            std::ptr::write(ptr, self.0);
669            sexp
670        }
671    }
672}
673// endregion
674
675// region: Named pair wrappers
676
677/// Wrap a tuple pair collection and convert it to a **named R list** (VECSXP).
678///
679/// Preserves insertion order and allows duplicate names (sequence semantics).
680///
681/// # Supported input types
682///
683/// | Input | Bounds |
684/// |-------|--------|
685/// | `Vec<(K, V)>` | `K: AsRef<str>`, `V: IntoR` |
686/// | `[(K, V); N]` | `K: AsRef<str>`, `V: IntoR` |
687/// | `&[(K, V)]` | `K: AsRef<str>`, `V: Clone + IntoR` |
688///
689/// # Example
690///
691/// ```ignore
692/// #[miniextendr]
693/// fn make_config() -> AsNamedList<Vec<(String, i32)>> {
694///     AsNamedList(vec![
695///         ("width".into(), 100),
696///         ("height".into(), 200),
697///     ])
698/// }
699/// // In R: make_config() returns list(width = 100L, height = 200L)
700/// ```
701#[derive(Debug, Clone)]
702pub struct AsNamedList<T>(pub T);
703
704impl<T> From<T> for AsNamedList<T> {
705    fn from(value: T) -> Self {
706        AsNamedList(value)
707    }
708}
709
710impl<K: AsRef<str>, V: IntoR> IntoR for AsNamedList<Vec<(K, V)>> {
711    type Error = std::convert::Infallible;
712
713    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
714        Ok(self.into_sexp())
715    }
716
717    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
718        self.try_into_sexp()
719    }
720
721    fn into_sexp(self) -> crate::ffi::SEXP {
722        let pairs: Vec<(K, crate::ffi::SEXP)> = self
723            .0
724            .into_iter()
725            .map(|(k, v)| (k, v.into_sexp()))
726            .collect();
727        List::from_raw_pairs(pairs).into_sexp()
728    }
729}
730
731impl<K: AsRef<str>, V: IntoR, const N: usize> IntoR for AsNamedList<[(K, V); N]> {
732    type Error = std::convert::Infallible;
733
734    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
735        Ok(self.into_sexp())
736    }
737
738    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
739        self.try_into_sexp()
740    }
741
742    fn into_sexp(self) -> crate::ffi::SEXP {
743        let pairs: Vec<(K, crate::ffi::SEXP)> = self
744            .0
745            .into_iter()
746            .map(|(k, v)| (k, v.into_sexp()))
747            .collect();
748        List::from_raw_pairs(pairs).into_sexp()
749    }
750}
751
752impl<K: AsRef<str>, V: Clone + IntoR> IntoR for AsNamedList<&[(K, V)]> {
753    type Error = std::convert::Infallible;
754
755    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
756        Ok(self.into_sexp())
757    }
758
759    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
760        self.try_into_sexp()
761    }
762
763    fn into_sexp(self) -> crate::ffi::SEXP {
764        let pairs: Vec<(&K, crate::ffi::SEXP)> = self
765            .0
766            .iter()
767            .map(|(k, v)| (k, v.clone().into_sexp()))
768            .collect();
769        List::from_raw_pairs(pairs).into_sexp()
770    }
771}
772
773/// Wrap a tuple pair collection and convert it to a **named atomic R vector**
774/// (INTSXP, REALSXP, LGLSXP, RAWSXP, or STRSXP).
775///
776/// Preserves insertion order and allows duplicate names (sequence semantics).
777/// Values must be homogeneous and implement [`AtomicElement`].
778///
779/// # Supported input types
780///
781/// | Input | Bounds |
782/// |-------|--------|
783/// | `Vec<(K, V)>` | `K: AsRef<str>`, `V: AtomicElement` |
784/// | `[(K, V); N]` | `K: AsRef<str>`, `V: AtomicElement` |
785/// | `&[(K, V)]` | `K: AsRef<str>`, `V: Clone + AtomicElement` |
786///
787/// # Example
788///
789/// ```ignore
790/// #[miniextendr]
791/// fn make_scores() -> AsNamedVector<Vec<(&str, f64)>> {
792///     AsNamedVector(vec![("alice", 95.0), ("bob", 87.5)])
793/// }
794/// // In R: make_scores() returns c(alice = 95.0, bob = 87.5)
795/// ```
796#[derive(Debug, Clone)]
797pub struct AsNamedVector<T>(pub T);
798
799impl<T> From<T> for AsNamedVector<T> {
800    fn from(value: T) -> Self {
801        AsNamedVector(value)
802    }
803}
804
805impl<K: AsRef<str>, V: AtomicElement> IntoR for AsNamedVector<Vec<(K, V)>> {
806    type Error = std::convert::Infallible;
807
808    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
809        Ok(self.into_sexp())
810    }
811
812    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
813        self.try_into_sexp()
814    }
815
816    fn into_sexp(self) -> crate::ffi::SEXP {
817        named_vector_from_pairs(self.0)
818    }
819}
820
821impl<K: AsRef<str>, V: AtomicElement, const N: usize> IntoR for AsNamedVector<[(K, V); N]> {
822    type Error = std::convert::Infallible;
823
824    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
825        Ok(self.into_sexp())
826    }
827
828    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
829        self.try_into_sexp()
830    }
831
832    fn into_sexp(self) -> crate::ffi::SEXP {
833        named_vector_from_pairs(self.0)
834    }
835}
836
837impl<K: AsRef<str>, V: Clone + AtomicElement> IntoR for AsNamedVector<&[(K, V)]> {
838    type Error = std::convert::Infallible;
839
840    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
841        Ok(self.into_sexp())
842    }
843
844    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
845        self.try_into_sexp()
846    }
847
848    fn into_sexp(self) -> crate::ffi::SEXP {
849        let (keys, values): (Vec<&K>, Vec<V>) = self.0.iter().map(|(k, v)| (k, v.clone())).unzip();
850        let sexp = V::vec_to_sexp(values);
851        unsafe {
852            crate::ffi::Rf_protect(sexp);
853            crate::named_vector::set_names_on_sexp(sexp, &keys);
854            crate::ffi::Rf_unprotect(1);
855        }
856        sexp
857    }
858}
859
860/// Shared helper: build a named atomic vector from an owning iterator of (key, value) pairs.
861fn named_vector_from_pairs<K, V>(pairs: impl IntoIterator<Item = (K, V)>) -> crate::ffi::SEXP
862where
863    K: AsRef<str>,
864    V: AtomicElement,
865{
866    let (keys, values): (Vec<K>, Vec<V>) = pairs.into_iter().unzip();
867    let sexp = V::vec_to_sexp(values);
868    unsafe {
869        crate::ffi::Rf_protect(sexp);
870        crate::named_vector::set_names_on_sexp(sexp, &keys);
871        crate::ffi::Rf_unprotect(1);
872    }
873    sexp
874}
875// endregion
876
877// region: Extension traits for ergonomic wrapping
878//
879// These extension traits provide method-style wrapping that works even when
880// the destination type isn't constrained (i.e., `value.wrap_list()` instead
881// of `value.into()` which requires type inference).
882//
883// ```ignore
884// // These all work without type annotations:
885// let wrapped = my_struct.wrap_list();
886// let ptr = my_value.wrap_external_ptr();
887// let native = my_num.wrap_r_native();
888// ```
889
890/// Extension trait for wrapping values as [`AsList`].
891///
892/// This trait is automatically implemented for all types that implement [`IntoList`].
893///
894/// # Example
895///
896/// ```ignore
897/// use miniextendr_api::convert::AsListExt;
898///
899/// #[derive(IntoList)]
900/// struct Point { x: f64, y: f64 }
901///
902/// let point = Point { x: 1.0, y: 2.0 };
903/// let wrapped: AsList<Point> = point.wrap_list();
904/// ```
905pub trait AsListExt: IntoList + Sized {
906    /// Wrap `self` in [`AsList`] for R list conversion.
907    fn wrap_list(self) -> AsList<Self> {
908        AsList(self)
909    }
910}
911
912impl<T: IntoList> AsListExt for T {}
913
914/// Extension trait for wrapping values as [`ToDataFrame`].
915///
916/// This trait is automatically implemented for all types that implement [`IntoDataFrame`].
917///
918/// # Example
919///
920/// ```ignore
921/// use miniextendr_api::convert::ToDataFrameExt;
922///
923/// struct TimeSeries {
924///     timestamps: Vec<f64>,
925///     values: Vec<f64>,
926/// }
927///
928/// impl IntoDataFrame for TimeSeries {
929///     fn into_data_frame(self) -> List {
930///         List::from_pairs(vec![
931///             ("timestamp", self.timestamps),
932///             ("value", self.values),
933///         ])
934///         .set_class_str(&["data.frame"])
935///         .set_row_names_int(self.timestamps.len())
936///     }
937/// }
938///
939/// let ts = TimeSeries { timestamps: vec![1.0, 2.0], values: vec![10.0, 20.0] };
940/// let wrapped: ToDataFrame<TimeSeries> = ts.to_data_frame();
941/// ```
942pub trait ToDataFrameExt: IntoDataFrame + Sized {
943    /// Wrap `self` in [`ToDataFrame`] for R data.frame conversion.
944    fn to_data_frame(self) -> ToDataFrame<Self> {
945        ToDataFrame(self)
946    }
947}
948
949impl<T: IntoDataFrame> ToDataFrameExt for T {}
950
951/// Extension trait for wrapping values as [`AsExternalPtr`].
952///
953/// This trait is automatically implemented for all types that implement [`IntoExternalPtr`].
954///
955/// # Example
956///
957/// ```ignore
958/// use miniextendr_api::convert::AsExternalPtrExt;
959///
960/// #[derive(ExternalPtr)]
961/// struct Connection { handle: u64 }
962///
963/// let conn = Connection { handle: 42 };
964/// let wrapped: AsExternalPtr<Connection> = conn.wrap_external_ptr();
965/// ```
966pub trait AsExternalPtrExt: IntoExternalPtr + Sized {
967    /// Wrap `self` in [`AsExternalPtr`] for R external pointer conversion.
968    fn wrap_external_ptr(self) -> AsExternalPtr<Self> {
969        AsExternalPtr(self)
970    }
971}
972
973impl<T: IntoExternalPtr> AsExternalPtrExt for T {}
974
975/// Extension trait for wrapping values as [`AsRNative`].
976///
977/// This trait is automatically implemented for all types that implement [`RNativeType`].
978///
979/// # Example
980///
981/// ```ignore
982/// use miniextendr_api::convert::AsRNativeExt;
983///
984/// let x: f64 = 42.5;
985/// let wrapped: AsRNative<f64> = x.wrap_r_native();
986/// ```
987pub trait AsRNativeExt: RNativeType + Sized {
988    /// Wrap `self` in [`AsRNative`] for native R scalar conversion.
989    fn wrap_r_native(self) -> AsRNative<Self> {
990        AsRNative(self)
991    }
992}
993
994impl<T: RNativeType> AsRNativeExt for T {}
995
996/// Extension trait for wrapping tuple pair collections as [`AsNamedList`].
997///
998/// # Example
999///
1000/// ```ignore
1001/// let pairs = vec![("x".to_string(), 1i32), ("y".to_string(), 2i32)];
1002/// let wrapped = pairs.wrap_named_list();
1003/// ```
1004pub trait AsNamedListExt: Sized {
1005    /// Wrap `self` in [`AsNamedList`] for named R list conversion.
1006    fn wrap_named_list(self) -> AsNamedList<Self> {
1007        AsNamedList(self)
1008    }
1009}
1010
1011impl<K: AsRef<str>, V: IntoR> AsNamedListExt for Vec<(K, V)> {}
1012impl<K: AsRef<str>, V: IntoR, const N: usize> AsNamedListExt for [(K, V); N] {}
1013impl<K: AsRef<str>, V: Clone + IntoR> AsNamedListExt for &[(K, V)] {}
1014
1015/// Extension trait for wrapping tuple pair collections as [`AsNamedVector`].
1016///
1017/// # Example
1018///
1019/// ```ignore
1020/// let pairs = vec![("alice".to_string(), 95.0f64), ("bob".to_string(), 87.5)];
1021/// let wrapped = pairs.wrap_named_vector();
1022/// ```
1023pub trait AsNamedVectorExt: Sized {
1024    /// Wrap `self` in [`AsNamedVector`] for named atomic R vector conversion.
1025    fn wrap_named_vector(self) -> AsNamedVector<Self> {
1026        AsNamedVector(self)
1027    }
1028}
1029
1030impl<K: AsRef<str>, V: AtomicElement> AsNamedVectorExt for Vec<(K, V)> {}
1031impl<K: AsRef<str>, V: AtomicElement, const N: usize> AsNamedVectorExt for [(K, V); N] {}
1032impl<K: AsRef<str>, V: Clone + AtomicElement> AsNamedVectorExt for &[(K, V)] {}
1033// endregion
1034
1035// region: Display/FromStr trait adapters
1036
1037/// Wrap a `T: Display` and convert it to an R character scalar.
1038///
1039/// Any type implementing `std::fmt::Display` can be returned to R as a string
1040/// without implementing miniextendr traits.
1041///
1042/// # Example
1043///
1044/// ```ignore
1045/// use std::net::IpAddr;
1046///
1047/// #[miniextendr]
1048/// fn format_ip(ip: &str) -> AsDisplay<IpAddr> {
1049///     AsDisplay(ip.parse().unwrap())
1050/// }
1051/// // R gets: "192.168.1.1"
1052/// ```
1053#[derive(Debug, Clone, Copy)]
1054pub struct AsDisplay<T>(pub T);
1055
1056impl<T: std::fmt::Display> IntoR for AsDisplay<T> {
1057    type Error = std::convert::Infallible;
1058
1059    #[inline]
1060    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
1061        Ok(self.0.to_string().into_sexp())
1062    }
1063
1064    #[inline]
1065    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
1066        Ok(unsafe { self.0.to_string().into_sexp_unchecked() })
1067    }
1068}
1069
1070/// Wrap a `Vec<T: Display>` and convert it to an R character vector.
1071///
1072/// # Example
1073///
1074/// ```ignore
1075/// #[miniextendr]
1076/// fn format_errors(errors: Vec<std::io::Error>) -> AsDisplayVec<std::io::Error> {
1077///     AsDisplayVec(errors)
1078/// }
1079/// ```
1080#[derive(Debug, Clone)]
1081pub struct AsDisplayVec<T>(pub Vec<T>);
1082
1083impl<T: std::fmt::Display> IntoR for AsDisplayVec<T> {
1084    type Error = std::convert::Infallible;
1085
1086    #[inline]
1087    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
1088        let strings: Vec<String> = self.0.into_iter().map(|x| x.to_string()).collect();
1089        Ok(strings.into_sexp())
1090    }
1091
1092    #[inline]
1093    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
1094        let strings: Vec<String> = self.0.into_iter().map(|x| x.to_string()).collect();
1095        Ok(unsafe { strings.into_sexp_unchecked() })
1096    }
1097}
1098
1099/// Wrap a parsed `T: FromStr` from an R character scalar.
1100///
1101/// Pass an R character scalar and it will be parsed into `T` via `str::parse()`.
1102///
1103/// # Example
1104///
1105/// ```ignore
1106/// use std::net::IpAddr;
1107///
1108/// #[miniextendr]
1109/// fn check_ip(addr: AsFromStr<IpAddr>) -> bool {
1110///     addr.0.is_loopback()
1111/// }
1112/// // R: check_ip("127.0.0.1") → TRUE
1113/// ```
1114#[derive(Debug, Clone)]
1115pub struct AsFromStr<T>(pub T);
1116
1117impl<T: std::str::FromStr> crate::from_r::TryFromSexp for AsFromStr<T>
1118where
1119    T::Err: std::fmt::Display,
1120{
1121    type Error = crate::from_r::SexpError;
1122
1123    fn try_from_sexp(sexp: crate::ffi::SEXP) -> Result<Self, Self::Error> {
1124        let s: &str = crate::from_r::TryFromSexp::try_from_sexp(sexp)?;
1125        let value = s
1126            .parse::<T>()
1127            .map_err(|e| crate::from_r::SexpError::InvalidValue(format!("{e}")))?;
1128        Ok(AsFromStr(value))
1129    }
1130
1131    unsafe fn try_from_sexp_unchecked(sexp: crate::ffi::SEXP) -> Result<Self, Self::Error> {
1132        let s: &str = unsafe { crate::from_r::TryFromSexp::try_from_sexp_unchecked(sexp)? };
1133        let value = s
1134            .parse::<T>()
1135            .map_err(|e| crate::from_r::SexpError::InvalidValue(format!("{e}")))?;
1136        Ok(AsFromStr(value))
1137    }
1138}
1139
1140/// Wrap a `Vec<T: FromStr>` parsed from an R character vector.
1141///
1142/// Each element of the R character vector is parsed into `T`.
1143/// All parse errors are collected with their indices.
1144///
1145/// # Example
1146///
1147/// ```ignore
1148/// use std::net::IpAddr;
1149///
1150/// #[miniextendr]
1151/// fn parse_ips(addrs: AsFromStrVec<IpAddr>) -> Vec<bool> {
1152///     addrs.0.into_iter().map(|ip| ip.is_loopback()).collect()
1153/// }
1154/// // R: parse_ips(c("127.0.0.1", "8.8.8.8")) → c(TRUE, FALSE)
1155/// ```
1156#[derive(Debug, Clone)]
1157pub struct AsFromStrVec<T>(pub Vec<T>);
1158
1159impl<T: std::str::FromStr> crate::from_r::TryFromSexp for AsFromStrVec<T>
1160where
1161    T::Err: std::fmt::Display,
1162{
1163    type Error = crate::from_r::SexpError;
1164
1165    fn try_from_sexp(sexp: crate::ffi::SEXP) -> Result<Self, Self::Error> {
1166        let strings: Vec<String> = crate::from_r::TryFromSexp::try_from_sexp(sexp)?;
1167        let mut result = Vec::with_capacity(strings.len());
1168        let mut errors = Vec::new();
1169        for (i, s) in strings.iter().enumerate() {
1170            match s.parse::<T>() {
1171                Ok(v) => result.push(v),
1172                Err(e) => errors.push(format!("index {i}: {e}")),
1173            }
1174        }
1175        if errors.is_empty() {
1176            Ok(AsFromStrVec(result))
1177        } else {
1178            Err(crate::from_r::SexpError::InvalidValue(format!(
1179                "parse errors: {}",
1180                errors.join("; ")
1181            )))
1182        }
1183    }
1184
1185    unsafe fn try_from_sexp_unchecked(sexp: crate::ffi::SEXP) -> Result<Self, Self::Error> {
1186        let strings: Vec<String> =
1187            unsafe { crate::from_r::TryFromSexp::try_from_sexp_unchecked(sexp)? };
1188        let mut result = Vec::with_capacity(strings.len());
1189        let mut errors = Vec::new();
1190        for (i, s) in strings.iter().enumerate() {
1191            match s.parse::<T>() {
1192                Ok(v) => result.push(v),
1193                Err(e) => errors.push(format!("index {i}: {e}")),
1194            }
1195        }
1196        if errors.is_empty() {
1197            Ok(AsFromStrVec(result))
1198        } else {
1199            Err(crate::from_r::SexpError::InvalidValue(format!(
1200                "parse errors: {}",
1201                errors.join("; ")
1202            )))
1203        }
1204    }
1205}
1206// endregion
1207
1208// region: Collect — zero-allocation iterator-to-R-vector adapters
1209
1210/// Write an `ExactSizeIterator` of native R types directly into an R vector.
1211///
1212/// Skips the intermediate `Vec` allocation — the R vector is allocated once
1213/// and the iterator writes directly into it.
1214///
1215/// Requires `ExactSizeIterator` because R vectors must know their length
1216/// at allocation time.
1217///
1218/// # Example
1219///
1220/// ```ignore
1221/// #[miniextendr]
1222/// fn sines(n: i32) -> Collect<impl ExactSizeIterator<Item = f64>> {
1223///     Collect((0..n).map(|i| (i as f64).sin()))
1224/// }
1225/// ```
1226pub struct Collect<I>(pub I);
1227
1228impl<I, T> IntoR for Collect<I>
1229where
1230    I: ExactSizeIterator<Item = T>,
1231    T: crate::ffi::RNativeType,
1232{
1233    type Error = std::convert::Infallible;
1234
1235    #[inline]
1236    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
1237        Ok(self.into_sexp())
1238    }
1239
1240    #[inline]
1241    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
1242        Ok(unsafe { self.into_sexp_unchecked() })
1243    }
1244
1245    #[inline]
1246    fn into_sexp(self) -> crate::ffi::SEXP {
1247        unsafe {
1248            let (sexp, dst) = crate::into_r::alloc_r_vector::<T>(self.0.len());
1249            for (slot, val) in dst.iter_mut().zip(self.0) {
1250                *slot = val;
1251            }
1252            sexp
1253        }
1254    }
1255
1256    #[inline]
1257    unsafe fn into_sexp_unchecked(self) -> crate::ffi::SEXP {
1258        unsafe {
1259            let (sexp, dst) = crate::into_r::alloc_r_vector_unchecked::<T>(self.0.len());
1260            for (slot, val) in dst.iter_mut().zip(self.0) {
1261                *slot = val;
1262            }
1263            sexp
1264        }
1265    }
1266}
1267
1268/// Write an `ExactSizeIterator` of `String` directly into an R character vector.
1269///
1270/// Strings require per-element CHARSXP allocation (no bulk `copy_from_slice`),
1271/// so this is a separate type from [`Collect`].
1272///
1273/// # Example
1274///
1275/// ```ignore
1276/// #[miniextendr]
1277/// fn upper(words: Vec<String>) -> CollectStrings<impl ExactSizeIterator<Item = String>> {
1278///     CollectStrings(words.into_iter().map(|w| w.to_uppercase()))
1279/// }
1280/// ```
1281pub struct CollectStrings<I>(pub I);
1282
1283impl<I> IntoR for CollectStrings<I>
1284where
1285    I: ExactSizeIterator<Item = String>,
1286{
1287    type Error = std::convert::Infallible;
1288
1289    #[inline]
1290    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
1291        // Collect String refs for str_iter_to_strsxp.
1292        let strings: Vec<String> = self.0.collect();
1293        Ok(crate::into_r::str_iter_to_strsxp(
1294            strings.iter().map(|s| s.as_str()),
1295        ))
1296    }
1297
1298    #[inline]
1299    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
1300        let strings: Vec<String> = self.0.collect();
1301        Ok(unsafe {
1302            crate::into_r::str_iter_to_strsxp_unchecked(strings.iter().map(|s| s.as_str()))
1303        })
1304    }
1305}
1306
1307/// Write an `ExactSizeIterator` of `Option<T>` directly into an R vector with NA support.
1308///
1309/// `None` values become `NA` in R. Works for `f64` and `i32`.
1310///
1311/// # Example
1312///
1313/// ```ignore
1314/// #[miniextendr]
1315/// fn with_gaps(n: i32) -> CollectNA<impl ExactSizeIterator<Item = Option<f64>>> {
1316///     CollectNA((0..n).map(|i| if i % 3 == 0 { None } else { Some(i as f64) }))
1317/// }
1318/// ```
1319pub struct CollectNA<I>(pub I);
1320
1321impl<I> IntoR for CollectNA<I>
1322where
1323    I: ExactSizeIterator<Item = Option<f64>>,
1324{
1325    type Error = std::convert::Infallible;
1326
1327    #[inline]
1328    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
1329        Ok(self.into_sexp())
1330    }
1331
1332    #[inline]
1333    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
1334        Ok(unsafe { self.into_sexp_unchecked() })
1335    }
1336
1337    #[inline]
1338    fn into_sexp(self) -> crate::ffi::SEXP {
1339        unsafe {
1340            let (sexp, dst) = crate::into_r::alloc_r_vector::<f64>(self.0.len());
1341            for (slot, val) in dst.iter_mut().zip(self.0) {
1342                *slot = val.unwrap_or(crate::altrep_traits::NA_REAL);
1343            }
1344            sexp
1345        }
1346    }
1347
1348    #[inline]
1349    unsafe fn into_sexp_unchecked(self) -> crate::ffi::SEXP {
1350        unsafe {
1351            let (sexp, dst) = crate::into_r::alloc_r_vector_unchecked::<f64>(self.0.len());
1352            for (slot, val) in dst.iter_mut().zip(self.0) {
1353                *slot = val.unwrap_or(crate::altrep_traits::NA_REAL);
1354            }
1355            sexp
1356        }
1357    }
1358}
1359
1360/// Write an `ExactSizeIterator` of `Option<i32>` directly into an R integer vector with NA.
1361pub struct CollectNAInt<I>(pub I);
1362
1363impl<I> IntoR for CollectNAInt<I>
1364where
1365    I: ExactSizeIterator<Item = Option<i32>>,
1366{
1367    type Error = std::convert::Infallible;
1368
1369    #[inline]
1370    fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
1371        Ok(self.into_sexp())
1372    }
1373
1374    #[inline]
1375    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
1376        Ok(unsafe { self.into_sexp_unchecked() })
1377    }
1378
1379    #[inline]
1380    fn into_sexp(self) -> crate::ffi::SEXP {
1381        unsafe {
1382            let (sexp, dst) = crate::into_r::alloc_r_vector::<i32>(self.0.len());
1383            for (slot, val) in dst.iter_mut().zip(self.0) {
1384                *slot = val.unwrap_or(crate::altrep_traits::NA_INTEGER);
1385            }
1386            sexp
1387        }
1388    }
1389
1390    #[inline]
1391    unsafe fn into_sexp_unchecked(self) -> crate::ffi::SEXP {
1392        unsafe {
1393            let (sexp, dst) = crate::into_r::alloc_r_vector_unchecked::<i32>(self.0.len());
1394            for (slot, val) in dst.iter_mut().zip(self.0) {
1395                *slot = val.unwrap_or(crate::altrep_traits::NA_INTEGER);
1396            }
1397            sexp
1398        }
1399    }
1400}
1401// endregion