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