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 /// Extract named column SEXPs from this DataFrame.
259 ///
260 /// Returns a `Vec<(String, SEXP)>` where each entry is a column name and
261 /// the raw SEXP for that column. The SEXPs are owned by the data frame SEXP
262 /// and **must** be protected by the caller before the data frame SEXP is
263 /// released.
264 ///
265 /// Used by `DataFrameRow`-derived enum code to flatten struct-typed fields.
266 /// The default implementation calls `into_data_frame()` and extracts the names
267 /// and elements from the resulting VECSXP. Override only if you need a more
268 /// efficient extraction path.
269 ///
270 /// # Safety
271 ///
272 /// This method calls R API functions and must run on the R main thread.
273 #[doc(hidden)]
274 fn into_named_columns(self) -> Vec<(String, crate::ffi::SEXP)>
275 where
276 Self: Sized,
277 {
278 use crate::ffi::SexpExt as _;
279 let list = self.into_data_frame();
280 let sexp = list.as_sexp();
281 let n = sexp.len();
282 let mut out = Vec::with_capacity(n);
283 let names_sexp = sexp.get_names();
284 let has_names = !names_sexp.is_nil();
285 for i in 0..(n as isize) {
286 let col_sexp = sexp.vector_elt(i);
287 let col_name = if has_names {
288 names_sexp.string_elt_str(i).unwrap_or("").to_string()
289 } else {
290 i.to_string()
291 };
292 out.push((col_name, col_sexp));
293 }
294 out
295 }
296}
297
298// region: Struct-field scatter helper
299
300/// Scatter a typed column SEXP from a dense inner data frame into a new
301/// SEXP of length `n_rows`, placing `NA`/`NULL` at rows not in `present_idx`.
302///
303/// This is called by `DataFrameRow`-derived enum code to flatten struct-typed
304/// variant fields into prefixed columns of the parent data frame.
305///
306/// The output type mirrors the input:
307/// - REALSXP → REALSXP (NA_real_ fill)
308/// - INTSXP → INTSXP (NA_integer_ fill)
309/// - LGLSXP → LGLSXP (NA_logical fill)
310/// - STRSXP → STRSXP (NA_character_ fill)
311/// - VECSXP → VECSXP (R_NilValue fill)
312/// - anything else → VECSXP (R_NilValue fill)
313///
314/// # Safety
315///
316/// Must be called on the R main thread. `src` must be a valid SEXP of length
317/// `>= present_idx.len()`. `n_rows` must equal the total row count of the
318/// parent data frame.
319#[doc(hidden)]
320pub unsafe fn scatter_column(
321 src: crate::ffi::SEXP,
322 present_idx: &[usize],
323 n_rows: usize,
324) -> crate::ffi::SEXP {
325 // SAFETY: caller guarantees R main thread; src is valid; n_rows is correct.
326 #[allow(unused_unsafe)]
327 unsafe {
328 use crate::ffi::{SEXPTYPE, SexpExt as _};
329
330 let stype = src.type_of();
331 let n_present = present_idx.len();
332
333 match stype {
334 SEXPTYPE::REALSXP => {
335 let out = crate::ffi::Rf_allocVector(SEXPTYPE::REALSXP, n_rows as isize);
336 // Fill with NA_real_ (R's NA for doubles is a specific NaN bit pattern;
337 // f64::NAN has the correct bit pattern since R uses a quiet NaN sentinel).
338 for i in 0..(n_rows as isize) {
339 out.set_real_elt(i, f64::NAN);
340 }
341 for (pi, &row_i) in present_idx.iter().enumerate() {
342 if pi < n_present {
343 out.set_real_elt(row_i as isize, src.real_elt(pi as isize));
344 }
345 }
346 out
347 }
348 SEXPTYPE::INTSXP => {
349 let out = crate::ffi::Rf_allocVector(SEXPTYPE::INTSXP, n_rows as isize);
350 for i in 0..(n_rows as isize) {
351 out.set_integer_elt(i, i32::MIN); // NA_integer_
352 }
353 for (pi, &row_i) in present_idx.iter().enumerate() {
354 if pi < n_present {
355 out.set_integer_elt(row_i as isize, src.integer_elt(pi as isize));
356 }
357 }
358 out
359 }
360 SEXPTYPE::LGLSXP => {
361 let out = crate::ffi::Rf_allocVector(SEXPTYPE::LGLSXP, n_rows as isize);
362 for i in 0..(n_rows as isize) {
363 out.set_logical_elt(i, i32::MIN); // NA_logical
364 }
365 for (pi, &row_i) in present_idx.iter().enumerate() {
366 if pi < n_present {
367 out.set_logical_elt(row_i as isize, src.logical_elt(pi as isize));
368 }
369 }
370 out
371 }
372 SEXPTYPE::STRSXP => {
373 let out = crate::ffi::Rf_allocVector(SEXPTYPE::STRSXP, n_rows as isize);
374 // Fill with NA_character_
375 for i in 0..(n_rows as isize) {
376 out.set_string_elt(i, crate::ffi::SEXP::na_string());
377 }
378 for (pi, &row_i) in present_idx.iter().enumerate() {
379 if pi < n_present {
380 let charsxp = src.string_elt(pi as isize);
381 out.set_string_elt(row_i as isize, charsxp);
382 }
383 }
384 out
385 }
386 SEXPTYPE::VECSXP => {
387 let out = crate::ffi::Rf_allocVector(SEXPTYPE::VECSXP, n_rows as isize);
388 // R_NilValue fill is automatic (Rf_allocVector zero-initialises VECSXP slots).
389 for (pi, &row_i) in present_idx.iter().enumerate() {
390 if pi < n_present {
391 let elt = src.vector_elt(pi as isize);
392 out.set_vector_elt(row_i as isize, elt);
393 }
394 }
395 out
396 }
397 _ => {
398 // Unknown/unsupported type — produce a VECSXP list-column.
399 // Cells for absent rows remain R_NilValue.
400 let out = crate::ffi::Rf_allocVector(SEXPTYPE::VECSXP, n_rows as isize);
401 for (pi, &row_i) in present_idx.iter().enumerate() {
402 if pi < n_present {
403 let elt = src.vector_elt(pi as isize);
404 out.set_vector_elt(row_i as isize, elt);
405 }
406 }
407 out
408 }
409 }
410 }
411}
412// endregion
413
414// region: Serde Row Wrapper
415
416/// Wrap a serde-serializable value for use as a data frame row.
417///
418/// This wrapper implements [`IntoList`] via serde serialization, allowing
419/// types that implement `serde::Serialize` to be used with [`DataFrame`]
420/// without manually implementing [`IntoList`].
421///
422/// # Feature Flag
423///
424/// Requires the `serde` feature to be enabled.
425///
426/// # Example
427///
428/// ```ignore
429/// use miniextendr_api::{miniextendr, convert::{AsSerializeRow, DataFrame}};
430/// use serde::Serialize;
431///
432/// #[derive(Serialize)]
433/// struct Measurement {
434/// time: f64,
435/// value: f64,
436/// }
437///
438/// #[miniextendr]
439/// fn get_data() -> DataFrame<AsSerializeRow<Measurement>> {
440/// DataFrame::from_rows(vec![
441/// AsSerializeRow(Measurement { time: 1.0, value: 10.0 }),
442/// AsSerializeRow(Measurement { time: 2.0, value: 20.0 }),
443/// ])
444/// }
445/// ```
446#[cfg(feature = "serde")]
447#[derive(Debug, Clone, Copy)]
448pub struct AsSerializeRow<T: serde::Serialize>(pub T);
449
450#[cfg(feature = "serde")]
451impl<T: serde::Serialize> From<T> for AsSerializeRow<T> {
452 fn from(value: T) -> Self {
453 AsSerializeRow(value)
454 }
455}
456
457#[cfg(feature = "serde")]
458impl<T: serde::Serialize> IntoList for AsSerializeRow<T> {
459 fn into_list(self) -> List {
460 use crate::ffi::{SEXPTYPE, SexpExt};
461 use crate::serde::RSerializer;
462 match RSerializer::to_sexp(&self.0) {
463 Ok(sexp) => {
464 if sexp.type_of() == SEXPTYPE::VECSXP {
465 unsafe { List::from_raw(sexp) }
466 } else {
467 // Non-list SEXP (e.g., scalar) — wrap in a single-element list
468 List::from_raw_values(vec![sexp])
469 }
470 }
471 Err(e) => {
472 panic!("AsSerializeRow: serde serialization failed: {e}");
473 }
474 }
475 }
476}
477
478/// Type alias for a [`DataFrame`] of serde-serializable rows.
479///
480/// This is equivalent to `DataFrame<AsSerializeRow<T>>` but more concise.
481///
482/// # Example
483///
484/// ```ignore
485/// use miniextendr_api::{miniextendr, SerializeDataFrame};
486/// use serde::Serialize;
487///
488/// #[derive(Serialize)]
489/// struct Person {
490/// name: String,
491/// age: i32,
492/// }
493///
494/// #[miniextendr]
495/// fn get_people() -> SerializeDataFrame<Person> {
496/// let people = vec![
497/// Person { name: "Alice".into(), age: 30 },
498/// Person { name: "Bob".into(), age: 25 },
499/// ];
500/// SerializeDataFrame::from_serialize(people)
501/// }
502/// ```
503#[cfg(feature = "serde")]
504pub type SerializeDataFrame<T> = DataFrame<AsSerializeRow<T>>;
505// endregion
506
507// region: Data Frame Row Conversion
508
509/// Convert row-oriented data into a column-oriented R data.frame.
510///
511/// This type collects a sequence of row elements (structs implementing [`IntoList`])
512/// and transposes them into column vectors suitable for creating an R data.frame.
513///
514/// # Example
515///
516/// ```ignore
517/// use miniextendr_api::{miniextendr, convert::DataFrame};
518///
519/// #[derive(IntoList)]
520/// struct Person {
521/// name: String,
522/// age: i32,
523/// height: f64,
524/// }
525///
526/// #[miniextendr]
527/// fn make_people() -> DataFrame<Person> {
528/// DataFrame::from_rows(vec![
529/// Person { name: "Alice".into(), age: 30, height: 165.0 },
530/// Person { name: "Bob".into(), age: 25, height: 180.0 },
531/// Person { name: "Carol".into(), age: 35, height: 170.0 },
532/// ])
533/// }
534/// // In R: make_people() returns a data.frame with 3 rows and columns: name, age, height
535/// ```
536///
537/// # Row-oriented to Column-oriented
538///
539/// R data frames are column-oriented (each column is a vector), but data is often
540/// produced row-by-row in Rust. `DataFrame` handles the transposition:
541///
542/// ```text
543/// Input (row-oriented): Output (column-oriented):
544/// Row 1: {name: "A", age: 30} name column: ["A", "B", "C"]
545/// Row 2: {name: "B", age: 25} → age column: [30, 25, 35]
546/// Row 3: {name: "C", age: 35}
547/// ```
548#[derive(Debug, Clone)]
549pub struct DataFrame<T: IntoList> {
550 rows: Vec<T>,
551}
552
553impl<T: IntoList> DataFrame<T> {
554 /// Create a new `DataFrame` from a vector of row elements.
555 pub fn from_rows(rows: Vec<T>) -> Self {
556 Self { rows }
557 }
558
559 /// Create an empty `DataFrame`.
560 pub fn new() -> Self {
561 Self { rows: Vec::new() }
562 }
563
564 /// Add a row to the data frame.
565 pub fn push(&mut self, row: T) {
566 self.rows.push(row);
567 }
568
569 /// Get the number of rows.
570 pub fn len(&self) -> usize {
571 self.rows.len()
572 }
573
574 /// Check if empty.
575 pub fn is_empty(&self) -> bool {
576 self.rows.is_empty()
577 }
578}
579
580#[cfg(feature = "serde")]
581impl<T: serde::Serialize> DataFrame<AsSerializeRow<T>> {
582 /// Create a DataFrame from serde-serializable rows.
583 ///
584 /// This is a convenience method that wraps each row in [`AsSerializeRow`]
585 /// automatically, avoiding the need to manually map over the input.
586 ///
587 /// # Example
588 ///
589 /// ```ignore
590 /// use miniextendr_api::DataFrame;
591 /// use serde::Serialize;
592 ///
593 /// #[derive(Serialize)]
594 /// struct Person { name: String, age: i32 }
595 ///
596 /// let people = vec![
597 /// Person { name: "Alice".into(), age: 30 },
598 /// Person { name: "Bob".into(), age: 25 },
599 /// ];
600 ///
601 /// // Instead of:
602 /// // DataFrame::from_iter(people.into_iter().map(AsSerializeRow))
603 ///
604 /// // Just write:
605 /// let df = DataFrame::from_serialize(people);
606 /// ```
607 pub fn from_serialize(rows: impl IntoIterator<Item = T>) -> Self {
608 Self::from_iter(rows.into_iter().map(AsSerializeRow))
609 }
610}
611
612impl<T: IntoList> Default for DataFrame<T> {
613 fn default() -> Self {
614 Self::new()
615 }
616}
617
618impl<T: IntoList> FromIterator<T> for DataFrame<T> {
619 fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
620 Self {
621 rows: iter.into_iter().collect(),
622 }
623 }
624}
625
626impl<T: IntoList> IntoDataFrame for DataFrame<T> {
627 fn into_data_frame(self) -> List {
628 if self.rows.is_empty() {
629 // Empty data frame
630 return List::from_raw_pairs(Vec::<(&str, crate::ffi::SEXP)>::new())
631 .set_data_frame_class()
632 .set_row_names_int(0);
633 }
634
635 let mut n_protect: i32 = 0;
636
637 // Convert all rows to lists, protecting each from GC.
638 let lists: Vec<List> = self
639 .rows
640 .into_iter()
641 .map(|row| {
642 let list = row.into_list();
643 unsafe { crate::ffi::Rf_protect(list.as_sexp()) };
644 n_protect += 1;
645 list
646 })
647 .collect();
648 let n_rows = lists.len() as isize;
649
650 // Get column names from the first row
651 let first_names_sexp = lists[0].names();
652 if first_names_sexp.is_none() {
653 unsafe { crate::ffi::Rf_unprotect(n_protect) };
654 panic!("cannot create data frame from unnamed list elements");
655 }
656
657 // Extract column names as Vec<String>
658 let names_sexp = first_names_sexp.expect("checked is_none above");
659 let n_cols = names_sexp.xlength();
660 let mut col_names = Vec::with_capacity(n_cols as usize);
661 for i in 0..n_cols {
662 unsafe {
663 let name_sexp = names_sexp.string_elt(i);
664 let name_ptr = name_sexp.r_char();
665 let name_cstr = std::ffi::CStr::from_ptr(name_ptr);
666 if let Ok(s) = name_cstr.to_str() {
667 col_names.push(s.to_string());
668 }
669 }
670 }
671
672 // Transpose: collect values by column.
673 // Element SEXPs from get_named are children of protected row lists,
674 // so they don't need individual protection.
675 use std::collections::HashMap;
676 let mut columns: HashMap<String, Vec<crate::ffi::SEXP>> =
677 HashMap::with_capacity(col_names.len());
678 for name in &col_names {
679 columns.insert(name.clone(), Vec::with_capacity(n_rows as usize));
680 }
681
682 for list in &lists {
683 for name in &col_names {
684 let value = list
685 .get_named::<crate::ffi::SEXP>(name)
686 .unwrap_or(crate::ffi::SEXP::nil());
687 columns
688 .get_mut(name)
689 .expect("column inserted above")
690 .push(value);
691 }
692 }
693
694 // Build column vectors, protecting each from GC.
695 // Coalesce homogeneous length-1 scalars into atomic vectors so that
696 // columns are INTSXP/REALSXP/LGLSXP/STRSXP instead of VECSXP (list).
697 let mut df_pairs: Vec<(String, crate::ffi::SEXP)> = Vec::with_capacity(col_names.len());
698 for name in col_names {
699 let col_values = columns.remove(&name).expect("column inserted above");
700 let col_sexp = List::from_scalars_or_list(&col_values).as_sexp();
701 unsafe { crate::ffi::Rf_protect(col_sexp) };
702 n_protect += 1;
703 df_pairs.push((name, col_sexp));
704 }
705
706 let result = List::from_raw_pairs(df_pairs)
707 .set_data_frame_class()
708 .set_row_names_int(n_rows as usize);
709 unsafe { crate::ffi::Rf_unprotect(n_protect) };
710 result
711 }
712}
713
714/// Wrap a value and convert it to an R external pointer when returned from Rust.
715///
716/// Use this wrapper when you want to return a Rust value as an opaque pointer
717/// that R code can pass back to Rust functions later.
718///
719/// # Example
720///
721/// ```ignore
722/// struct Connection { handle: u64 }
723///
724/// impl IntoExternalPtr for Connection { /* ... */ }
725///
726/// #[miniextendr]
727/// fn open_connection(path: &str) -> AsExternalPtr<Connection> {
728/// AsExternalPtr(Connection { handle: 42 })
729/// }
730/// // In R: open_connection("foo") returns an external pointer
731/// ```
732#[derive(Debug, Clone, Copy)]
733pub struct AsExternalPtr<T: IntoExternalPtr>(pub T);
734
735impl<T: IntoExternalPtr> From<T> for AsExternalPtr<T> {
736 fn from(value: T) -> Self {
737 AsExternalPtr(value)
738 }
739}
740
741impl<T: IntoExternalPtr> IntoR for AsExternalPtr<T> {
742 type Error = std::convert::Infallible;
743
744 #[inline]
745 fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
746 Ok(self.into_sexp())
747 }
748
749 #[inline]
750 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
751 self.try_into_sexp()
752 }
753
754 #[inline]
755 fn into_sexp(self) -> crate::ffi::SEXP {
756 ExternalPtr::new(self.0).into_sexp()
757 }
758}
759
760/// Wrap a scalar [`RNativeType`] and force native R vector conversion.
761///
762/// This creates a length-1 R vector containing the scalar value. Use this when
763/// you want to ensure a value is converted to its native R representation (e.g.,
764/// `i32` → integer vector, `f64` → numeric vector) rather than another path
765/// like `IntoExternalPtr`.
766///
767/// # Example
768///
769/// ```ignore
770/// #[derive(Clone, Copy, RNativeType)]
771/// struct Meters(f64);
772///
773/// #[miniextendr]
774/// fn distance() -> AsRNative<Meters> {
775/// AsRNative(Meters(42.5))
776/// }
777/// // In R: distance() returns 42.5 (numeric vector of length 1)
778/// ```
779///
780/// # Performance
781///
782/// This wrapper directly allocates an R vector and writes the value,
783/// avoiding intermediate Rust allocations.
784#[derive(Debug, Clone, Copy)]
785pub struct AsRNative<T: RNativeType>(pub T);
786
787impl<T: RNativeType> From<T> for AsRNative<T> {
788 fn from(value: T) -> Self {
789 AsRNative(value)
790 }
791}
792
793impl<T: RNativeType> IntoR for AsRNative<T> {
794 type Error = std::convert::Infallible;
795
796 #[inline]
797 fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
798 Ok(self.into_sexp())
799 }
800
801 #[inline]
802 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
803 Ok(unsafe { self.into_sexp_unchecked() })
804 }
805
806 #[inline]
807 fn into_sexp(self) -> crate::ffi::SEXP {
808 // Directly allocate a length-1 R vector and write the scalar value.
809 // This avoids the intermediate Rust Vec allocation.
810 unsafe {
811 let sexp = crate::ffi::Rf_allocVector(T::SEXP_TYPE, 1);
812 let ptr = T::dataptr_mut(sexp);
813 std::ptr::write(ptr, self.0);
814 sexp
815 }
816 }
817
818 #[inline]
819 unsafe fn into_sexp_unchecked(self) -> crate::ffi::SEXP {
820 unsafe {
821 let sexp = crate::ffi::Rf_allocVector_unchecked(T::SEXP_TYPE, 1);
822 let ptr = T::dataptr_mut(sexp);
823 std::ptr::write(ptr, self.0);
824 sexp
825 }
826 }
827}
828// endregion
829
830// region: Named pair wrappers
831
832/// Wrap a tuple pair collection and convert it to a **named R list** (VECSXP).
833///
834/// Preserves insertion order and allows duplicate names (sequence semantics).
835///
836/// # Supported input types
837///
838/// | Input | Bounds |
839/// |-------|--------|
840/// | `Vec<(K, V)>` | `K: AsRef<str>`, `V: IntoR` |
841/// | `[(K, V); N]` | `K: AsRef<str>`, `V: IntoR` |
842/// | `&[(K, V)]` | `K: AsRef<str>`, `V: Clone + IntoR` |
843///
844/// # Example
845///
846/// ```ignore
847/// #[miniextendr]
848/// fn make_config() -> AsNamedList<Vec<(String, i32)>> {
849/// AsNamedList(vec![
850/// ("width".into(), 100),
851/// ("height".into(), 200),
852/// ])
853/// }
854/// // In R: make_config() returns list(width = 100L, height = 200L)
855/// ```
856#[derive(Debug, Clone)]
857pub struct AsNamedList<T>(pub T);
858
859impl<T> From<T> for AsNamedList<T> {
860 fn from(value: T) -> Self {
861 AsNamedList(value)
862 }
863}
864
865impl<K: AsRef<str>, V: IntoR> IntoR for AsNamedList<Vec<(K, V)>> {
866 type Error = std::convert::Infallible;
867
868 fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
869 Ok(self.into_sexp())
870 }
871
872 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
873 self.try_into_sexp()
874 }
875
876 fn into_sexp(self) -> crate::ffi::SEXP {
877 let pairs: Vec<(K, crate::ffi::SEXP)> = self
878 .0
879 .into_iter()
880 .map(|(k, v)| (k, v.into_sexp()))
881 .collect();
882 List::from_raw_pairs(pairs).into_sexp()
883 }
884}
885
886impl<K: AsRef<str>, V: IntoR, const N: usize> IntoR for AsNamedList<[(K, V); N]> {
887 type Error = std::convert::Infallible;
888
889 fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
890 Ok(self.into_sexp())
891 }
892
893 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
894 self.try_into_sexp()
895 }
896
897 fn into_sexp(self) -> crate::ffi::SEXP {
898 let pairs: Vec<(K, crate::ffi::SEXP)> = self
899 .0
900 .into_iter()
901 .map(|(k, v)| (k, v.into_sexp()))
902 .collect();
903 List::from_raw_pairs(pairs).into_sexp()
904 }
905}
906
907impl<K: AsRef<str>, V: Clone + IntoR> IntoR for AsNamedList<&[(K, V)]> {
908 type Error = std::convert::Infallible;
909
910 fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
911 Ok(self.into_sexp())
912 }
913
914 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
915 self.try_into_sexp()
916 }
917
918 fn into_sexp(self) -> crate::ffi::SEXP {
919 let pairs: Vec<(&K, crate::ffi::SEXP)> = self
920 .0
921 .iter()
922 .map(|(k, v)| (k, v.clone().into_sexp()))
923 .collect();
924 List::from_raw_pairs(pairs).into_sexp()
925 }
926}
927
928/// Wrap a tuple pair collection and convert it to a **named atomic R vector**
929/// (INTSXP, REALSXP, LGLSXP, RAWSXP, or STRSXP).
930///
931/// Preserves insertion order and allows duplicate names (sequence semantics).
932/// Values must be homogeneous and implement [`AtomicElement`].
933///
934/// # Supported input types
935///
936/// | Input | Bounds |
937/// |-------|--------|
938/// | `Vec<(K, V)>` | `K: AsRef<str>`, `V: AtomicElement` |
939/// | `[(K, V); N]` | `K: AsRef<str>`, `V: AtomicElement` |
940/// | `&[(K, V)]` | `K: AsRef<str>`, `V: Clone + AtomicElement` |
941///
942/// # Example
943///
944/// ```ignore
945/// #[miniextendr]
946/// fn make_scores() -> AsNamedVector<Vec<(&str, f64)>> {
947/// AsNamedVector(vec![("alice", 95.0), ("bob", 87.5)])
948/// }
949/// // In R: make_scores() returns c(alice = 95.0, bob = 87.5)
950/// ```
951#[derive(Debug, Clone)]
952pub struct AsNamedVector<T>(pub T);
953
954impl<T> From<T> for AsNamedVector<T> {
955 fn from(value: T) -> Self {
956 AsNamedVector(value)
957 }
958}
959
960impl<K: AsRef<str>, V: AtomicElement> IntoR for AsNamedVector<Vec<(K, V)>> {
961 type Error = std::convert::Infallible;
962
963 fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
964 Ok(self.into_sexp())
965 }
966
967 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
968 self.try_into_sexp()
969 }
970
971 fn into_sexp(self) -> crate::ffi::SEXP {
972 named_vector_from_pairs(self.0)
973 }
974}
975
976impl<K: AsRef<str>, V: AtomicElement, const N: usize> IntoR for AsNamedVector<[(K, V); N]> {
977 type Error = std::convert::Infallible;
978
979 fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
980 Ok(self.into_sexp())
981 }
982
983 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
984 self.try_into_sexp()
985 }
986
987 fn into_sexp(self) -> crate::ffi::SEXP {
988 named_vector_from_pairs(self.0)
989 }
990}
991
992impl<K: AsRef<str>, V: Clone + AtomicElement> IntoR for AsNamedVector<&[(K, V)]> {
993 type Error = std::convert::Infallible;
994
995 fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
996 Ok(self.into_sexp())
997 }
998
999 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
1000 self.try_into_sexp()
1001 }
1002
1003 fn into_sexp(self) -> crate::ffi::SEXP {
1004 let (keys, values): (Vec<&K>, Vec<V>) = self.0.iter().map(|(k, v)| (k, v.clone())).unzip();
1005 let sexp = V::vec_to_sexp(values);
1006 unsafe {
1007 crate::ffi::Rf_protect(sexp);
1008 crate::named_vector::set_names_on_sexp(sexp, &keys);
1009 crate::ffi::Rf_unprotect(1);
1010 }
1011 sexp
1012 }
1013}
1014
1015/// Shared helper: build a named atomic vector from an owning iterator of (key, value) pairs.
1016fn named_vector_from_pairs<K, V>(pairs: impl IntoIterator<Item = (K, V)>) -> crate::ffi::SEXP
1017where
1018 K: AsRef<str>,
1019 V: AtomicElement,
1020{
1021 let (keys, values): (Vec<K>, Vec<V>) = pairs.into_iter().unzip();
1022 let sexp = V::vec_to_sexp(values);
1023 unsafe {
1024 crate::ffi::Rf_protect(sexp);
1025 crate::named_vector::set_names_on_sexp(sexp, &keys);
1026 crate::ffi::Rf_unprotect(1);
1027 }
1028 sexp
1029}
1030// endregion
1031
1032// region: Extension traits for ergonomic wrapping
1033//
1034// These extension traits provide method-style wrapping that works even when
1035// the destination type isn't constrained (i.e., `value.wrap_list()` instead
1036// of `value.into()` which requires type inference).
1037//
1038// ```ignore
1039// // These all work without type annotations:
1040// let wrapped = my_struct.wrap_list();
1041// let ptr = my_value.wrap_external_ptr();
1042// let native = my_num.wrap_r_native();
1043// ```
1044
1045/// Extension trait for wrapping values as [`AsList`].
1046///
1047/// This trait is automatically implemented for all types that implement [`IntoList`].
1048///
1049/// # Example
1050///
1051/// ```ignore
1052/// use miniextendr_api::convert::AsListExt;
1053///
1054/// #[derive(IntoList)]
1055/// struct Point { x: f64, y: f64 }
1056///
1057/// let point = Point { x: 1.0, y: 2.0 };
1058/// let wrapped: AsList<Point> = point.wrap_list();
1059/// ```
1060pub trait AsListExt: IntoList + Sized {
1061 /// Wrap `self` in [`AsList`] for R list conversion.
1062 fn wrap_list(self) -> AsList<Self> {
1063 AsList(self)
1064 }
1065}
1066
1067impl<T: IntoList> AsListExt for T {}
1068
1069/// Extension trait for wrapping values as [`ToDataFrame`].
1070///
1071/// This trait is automatically implemented for all types that implement [`IntoDataFrame`].
1072///
1073/// # Example
1074///
1075/// ```ignore
1076/// use miniextendr_api::convert::ToDataFrameExt;
1077///
1078/// struct TimeSeries {
1079/// timestamps: Vec<f64>,
1080/// values: Vec<f64>,
1081/// }
1082///
1083/// impl IntoDataFrame for TimeSeries {
1084/// fn into_data_frame(self) -> List {
1085/// List::from_pairs(vec![
1086/// ("timestamp", self.timestamps),
1087/// ("value", self.values),
1088/// ])
1089/// .set_class_str(&["data.frame"])
1090/// .set_row_names_int(self.timestamps.len())
1091/// }
1092/// }
1093///
1094/// let ts = TimeSeries { timestamps: vec![1.0, 2.0], values: vec![10.0, 20.0] };
1095/// let wrapped: ToDataFrame<TimeSeries> = ts.to_data_frame();
1096/// ```
1097pub trait ToDataFrameExt: IntoDataFrame + Sized {
1098 /// Wrap `self` in [`ToDataFrame`] for R data.frame conversion.
1099 fn to_data_frame(self) -> ToDataFrame<Self> {
1100 ToDataFrame(self)
1101 }
1102}
1103
1104impl<T: IntoDataFrame> ToDataFrameExt for T {}
1105
1106/// Extension trait for wrapping values as [`AsExternalPtr`].
1107///
1108/// This trait is automatically implemented for all types that implement [`IntoExternalPtr`].
1109///
1110/// # Example
1111///
1112/// ```ignore
1113/// use miniextendr_api::convert::AsExternalPtrExt;
1114///
1115/// #[derive(ExternalPtr)]
1116/// struct Connection { handle: u64 }
1117///
1118/// let conn = Connection { handle: 42 };
1119/// let wrapped: AsExternalPtr<Connection> = conn.wrap_external_ptr();
1120/// ```
1121pub trait AsExternalPtrExt: IntoExternalPtr + Sized {
1122 /// Wrap `self` in [`AsExternalPtr`] for R external pointer conversion.
1123 fn wrap_external_ptr(self) -> AsExternalPtr<Self> {
1124 AsExternalPtr(self)
1125 }
1126}
1127
1128impl<T: IntoExternalPtr> AsExternalPtrExt for T {}
1129
1130/// Extension trait for wrapping values as [`AsRNative`].
1131///
1132/// This trait is automatically implemented for all types that implement [`RNativeType`].
1133///
1134/// # Example
1135///
1136/// ```ignore
1137/// use miniextendr_api::convert::AsRNativeExt;
1138///
1139/// let x: f64 = 42.5;
1140/// let wrapped: AsRNative<f64> = x.wrap_r_native();
1141/// ```
1142pub trait AsRNativeExt: RNativeType + Sized {
1143 /// Wrap `self` in [`AsRNative`] for native R scalar conversion.
1144 fn wrap_r_native(self) -> AsRNative<Self> {
1145 AsRNative(self)
1146 }
1147}
1148
1149impl<T: RNativeType> AsRNativeExt for T {}
1150
1151/// Extension trait for wrapping tuple pair collections as [`AsNamedList`].
1152///
1153/// # Example
1154///
1155/// ```ignore
1156/// let pairs = vec![("x".to_string(), 1i32), ("y".to_string(), 2i32)];
1157/// let wrapped = pairs.wrap_named_list();
1158/// ```
1159pub trait AsNamedListExt: Sized {
1160 /// Wrap `self` in [`AsNamedList`] for named R list conversion.
1161 fn wrap_named_list(self) -> AsNamedList<Self> {
1162 AsNamedList(self)
1163 }
1164}
1165
1166impl<K: AsRef<str>, V: IntoR> AsNamedListExt for Vec<(K, V)> {}
1167impl<K: AsRef<str>, V: IntoR, const N: usize> AsNamedListExt for [(K, V); N] {}
1168impl<K: AsRef<str>, V: Clone + IntoR> AsNamedListExt for &[(K, V)] {}
1169
1170/// Extension trait for wrapping tuple pair collections as [`AsNamedVector`].
1171///
1172/// # Example
1173///
1174/// ```ignore
1175/// let pairs = vec![("alice".to_string(), 95.0f64), ("bob".to_string(), 87.5)];
1176/// let wrapped = pairs.wrap_named_vector();
1177/// ```
1178pub trait AsNamedVectorExt: Sized {
1179 /// Wrap `self` in [`AsNamedVector`] for named atomic R vector conversion.
1180 fn wrap_named_vector(self) -> AsNamedVector<Self> {
1181 AsNamedVector(self)
1182 }
1183}
1184
1185impl<K: AsRef<str>, V: AtomicElement> AsNamedVectorExt for Vec<(K, V)> {}
1186impl<K: AsRef<str>, V: AtomicElement, const N: usize> AsNamedVectorExt for [(K, V); N] {}
1187impl<K: AsRef<str>, V: Clone + AtomicElement> AsNamedVectorExt for &[(K, V)] {}
1188// endregion
1189
1190// region: Display/FromStr trait adapters
1191
1192/// Wrap a `T: Display` and convert it to an R character scalar.
1193///
1194/// Any type implementing `std::fmt::Display` can be returned to R as a string
1195/// without implementing miniextendr traits.
1196///
1197/// # Example
1198///
1199/// ```ignore
1200/// use std::net::IpAddr;
1201///
1202/// #[miniextendr]
1203/// fn format_ip(ip: &str) -> AsDisplay<IpAddr> {
1204/// AsDisplay(ip.parse().unwrap())
1205/// }
1206/// // R gets: "192.168.1.1"
1207/// ```
1208#[derive(Debug, Clone, Copy)]
1209pub struct AsDisplay<T>(pub T);
1210
1211impl<T: std::fmt::Display> IntoR for AsDisplay<T> {
1212 type Error = std::convert::Infallible;
1213
1214 #[inline]
1215 fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
1216 Ok(self.0.to_string().into_sexp())
1217 }
1218
1219 #[inline]
1220 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
1221 Ok(unsafe { self.0.to_string().into_sexp_unchecked() })
1222 }
1223}
1224
1225/// Wrap a `Vec<T: Display>` and convert it to an R character vector.
1226///
1227/// # Example
1228///
1229/// ```ignore
1230/// #[miniextendr]
1231/// fn format_errors(errors: Vec<std::io::Error>) -> AsDisplayVec<std::io::Error> {
1232/// AsDisplayVec(errors)
1233/// }
1234/// ```
1235#[derive(Debug, Clone)]
1236pub struct AsDisplayVec<T>(pub Vec<T>);
1237
1238impl<T: std::fmt::Display> IntoR for AsDisplayVec<T> {
1239 type Error = std::convert::Infallible;
1240
1241 #[inline]
1242 fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
1243 let strings: Vec<String> = self.0.into_iter().map(|x| x.to_string()).collect();
1244 Ok(strings.into_sexp())
1245 }
1246
1247 #[inline]
1248 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
1249 let strings: Vec<String> = self.0.into_iter().map(|x| x.to_string()).collect();
1250 Ok(unsafe { strings.into_sexp_unchecked() })
1251 }
1252}
1253
1254/// Wrap a parsed `T: FromStr` from an R character scalar.
1255///
1256/// Pass an R character scalar and it will be parsed into `T` via `str::parse()`.
1257///
1258/// # Example
1259///
1260/// ```ignore
1261/// use std::net::IpAddr;
1262///
1263/// #[miniextendr]
1264/// fn check_ip(addr: AsFromStr<IpAddr>) -> bool {
1265/// addr.0.is_loopback()
1266/// }
1267/// // R: check_ip("127.0.0.1") → TRUE
1268/// ```
1269#[derive(Debug, Clone)]
1270pub struct AsFromStr<T>(pub T);
1271
1272impl<T: std::str::FromStr> crate::from_r::TryFromSexp for AsFromStr<T>
1273where
1274 T::Err: std::fmt::Display,
1275{
1276 type Error = crate::from_r::SexpError;
1277
1278 fn try_from_sexp(sexp: crate::ffi::SEXP) -> Result<Self, Self::Error> {
1279 let s: &str = crate::from_r::TryFromSexp::try_from_sexp(sexp)?;
1280 let value = s
1281 .parse::<T>()
1282 .map_err(|e| crate::from_r::SexpError::InvalidValue(format!("{e}")))?;
1283 Ok(AsFromStr(value))
1284 }
1285
1286 unsafe fn try_from_sexp_unchecked(sexp: crate::ffi::SEXP) -> Result<Self, Self::Error> {
1287 let s: &str = unsafe { crate::from_r::TryFromSexp::try_from_sexp_unchecked(sexp)? };
1288 let value = s
1289 .parse::<T>()
1290 .map_err(|e| crate::from_r::SexpError::InvalidValue(format!("{e}")))?;
1291 Ok(AsFromStr(value))
1292 }
1293}
1294
1295/// Wrap a `Vec<T: FromStr>` parsed from an R character vector.
1296///
1297/// Each element of the R character vector is parsed into `T`.
1298/// All parse errors are collected with their indices.
1299///
1300/// # Example
1301///
1302/// ```ignore
1303/// use std::net::IpAddr;
1304///
1305/// #[miniextendr]
1306/// fn parse_ips(addrs: AsFromStrVec<IpAddr>) -> Vec<bool> {
1307/// addrs.0.into_iter().map(|ip| ip.is_loopback()).collect()
1308/// }
1309/// // R: parse_ips(c("127.0.0.1", "8.8.8.8")) → c(TRUE, FALSE)
1310/// ```
1311#[derive(Debug, Clone)]
1312pub struct AsFromStrVec<T>(pub Vec<T>);
1313
1314impl<T: std::str::FromStr> crate::from_r::TryFromSexp for AsFromStrVec<T>
1315where
1316 T::Err: std::fmt::Display,
1317{
1318 type Error = crate::from_r::SexpError;
1319
1320 fn try_from_sexp(sexp: crate::ffi::SEXP) -> Result<Self, Self::Error> {
1321 let strings: Vec<String> = crate::from_r::TryFromSexp::try_from_sexp(sexp)?;
1322 let mut result = Vec::with_capacity(strings.len());
1323 let mut errors = Vec::new();
1324 for (i, s) in strings.iter().enumerate() {
1325 match s.parse::<T>() {
1326 Ok(v) => result.push(v),
1327 Err(e) => errors.push(format!("index {i}: {e}")),
1328 }
1329 }
1330 if errors.is_empty() {
1331 Ok(AsFromStrVec(result))
1332 } else {
1333 Err(crate::from_r::SexpError::InvalidValue(format!(
1334 "parse errors: {}",
1335 errors.join("; ")
1336 )))
1337 }
1338 }
1339
1340 unsafe fn try_from_sexp_unchecked(sexp: crate::ffi::SEXP) -> Result<Self, Self::Error> {
1341 let strings: Vec<String> =
1342 unsafe { crate::from_r::TryFromSexp::try_from_sexp_unchecked(sexp)? };
1343 let mut result = Vec::with_capacity(strings.len());
1344 let mut errors = Vec::new();
1345 for (i, s) in strings.iter().enumerate() {
1346 match s.parse::<T>() {
1347 Ok(v) => result.push(v),
1348 Err(e) => errors.push(format!("index {i}: {e}")),
1349 }
1350 }
1351 if errors.is_empty() {
1352 Ok(AsFromStrVec(result))
1353 } else {
1354 Err(crate::from_r::SexpError::InvalidValue(format!(
1355 "parse errors: {}",
1356 errors.join("; ")
1357 )))
1358 }
1359 }
1360}
1361// endregion
1362
1363// region: Collect — zero-allocation iterator-to-R-vector adapters
1364
1365/// Write an `ExactSizeIterator` of native R types directly into an R vector.
1366///
1367/// Skips the intermediate `Vec` allocation — the R vector is allocated once
1368/// and the iterator writes directly into it.
1369///
1370/// Requires `ExactSizeIterator` because R vectors must know their length
1371/// at allocation time.
1372///
1373/// # Example
1374///
1375/// ```ignore
1376/// #[miniextendr]
1377/// fn sines(n: i32) -> Collect<impl ExactSizeIterator<Item = f64>> {
1378/// Collect((0..n).map(|i| (i as f64).sin()))
1379/// }
1380/// ```
1381pub struct Collect<I>(pub I);
1382
1383impl<I, T> IntoR for Collect<I>
1384where
1385 I: ExactSizeIterator<Item = T>,
1386 T: crate::ffi::RNativeType,
1387{
1388 type Error = std::convert::Infallible;
1389
1390 #[inline]
1391 fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
1392 Ok(self.into_sexp())
1393 }
1394
1395 #[inline]
1396 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
1397 Ok(unsafe { self.into_sexp_unchecked() })
1398 }
1399
1400 #[inline]
1401 fn into_sexp(self) -> crate::ffi::SEXP {
1402 unsafe {
1403 let (sexp, dst) = crate::into_r::alloc_r_vector::<T>(self.0.len());
1404 for (slot, val) in dst.iter_mut().zip(self.0) {
1405 *slot = val;
1406 }
1407 sexp
1408 }
1409 }
1410
1411 #[inline]
1412 unsafe fn into_sexp_unchecked(self) -> crate::ffi::SEXP {
1413 unsafe {
1414 let (sexp, dst) = crate::into_r::alloc_r_vector_unchecked::<T>(self.0.len());
1415 for (slot, val) in dst.iter_mut().zip(self.0) {
1416 *slot = val;
1417 }
1418 sexp
1419 }
1420 }
1421}
1422
1423/// Write an `ExactSizeIterator` of `String` directly into an R character vector.
1424///
1425/// Strings require per-element CHARSXP allocation (no bulk `copy_from_slice`),
1426/// so this is a separate type from [`Collect`].
1427///
1428/// # Example
1429///
1430/// ```ignore
1431/// #[miniextendr]
1432/// fn upper(words: Vec<String>) -> CollectStrings<impl ExactSizeIterator<Item = String>> {
1433/// CollectStrings(words.into_iter().map(|w| w.to_uppercase()))
1434/// }
1435/// ```
1436pub struct CollectStrings<I>(pub I);
1437
1438impl<I> IntoR for CollectStrings<I>
1439where
1440 I: ExactSizeIterator<Item = String>,
1441{
1442 type Error = std::convert::Infallible;
1443
1444 #[inline]
1445 fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
1446 // Collect String refs for str_iter_to_strsxp.
1447 let strings: Vec<String> = self.0.collect();
1448 Ok(crate::into_r::str_iter_to_strsxp(
1449 strings.iter().map(|s| s.as_str()),
1450 ))
1451 }
1452
1453 #[inline]
1454 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
1455 let strings: Vec<String> = self.0.collect();
1456 Ok(unsafe {
1457 crate::into_r::str_iter_to_strsxp_unchecked(strings.iter().map(|s| s.as_str()))
1458 })
1459 }
1460}
1461
1462/// Write an `ExactSizeIterator` of `Option<T>` directly into an R vector with NA support.
1463///
1464/// `None` values become `NA` in R. Works for `f64` and `i32`.
1465///
1466/// # Example
1467///
1468/// ```ignore
1469/// #[miniextendr]
1470/// fn with_gaps(n: i32) -> CollectNA<impl ExactSizeIterator<Item = Option<f64>>> {
1471/// CollectNA((0..n).map(|i| if i % 3 == 0 { None } else { Some(i as f64) }))
1472/// }
1473/// ```
1474pub struct CollectNA<I>(pub I);
1475
1476impl<I> IntoR for CollectNA<I>
1477where
1478 I: ExactSizeIterator<Item = Option<f64>>,
1479{
1480 type Error = std::convert::Infallible;
1481
1482 #[inline]
1483 fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
1484 Ok(self.into_sexp())
1485 }
1486
1487 #[inline]
1488 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
1489 Ok(unsafe { self.into_sexp_unchecked() })
1490 }
1491
1492 #[inline]
1493 fn into_sexp(self) -> crate::ffi::SEXP {
1494 unsafe {
1495 let (sexp, dst) = crate::into_r::alloc_r_vector::<f64>(self.0.len());
1496 for (slot, val) in dst.iter_mut().zip(self.0) {
1497 *slot = val.unwrap_or(crate::altrep_traits::NA_REAL);
1498 }
1499 sexp
1500 }
1501 }
1502
1503 #[inline]
1504 unsafe fn into_sexp_unchecked(self) -> crate::ffi::SEXP {
1505 unsafe {
1506 let (sexp, dst) = crate::into_r::alloc_r_vector_unchecked::<f64>(self.0.len());
1507 for (slot, val) in dst.iter_mut().zip(self.0) {
1508 *slot = val.unwrap_or(crate::altrep_traits::NA_REAL);
1509 }
1510 sexp
1511 }
1512 }
1513}
1514
1515/// Write an `ExactSizeIterator` of `Option<i32>` directly into an R integer vector with NA.
1516pub struct CollectNAInt<I>(pub I);
1517
1518impl<I> IntoR for CollectNAInt<I>
1519where
1520 I: ExactSizeIterator<Item = Option<i32>>,
1521{
1522 type Error = std::convert::Infallible;
1523
1524 #[inline]
1525 fn try_into_sexp(self) -> Result<crate::ffi::SEXP, Self::Error> {
1526 Ok(self.into_sexp())
1527 }
1528
1529 #[inline]
1530 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::ffi::SEXP, Self::Error> {
1531 Ok(unsafe { self.into_sexp_unchecked() })
1532 }
1533
1534 #[inline]
1535 fn into_sexp(self) -> crate::ffi::SEXP {
1536 unsafe {
1537 let (sexp, dst) = crate::into_r::alloc_r_vector::<i32>(self.0.len());
1538 for (slot, val) in dst.iter_mut().zip(self.0) {
1539 *slot = val.unwrap_or(crate::altrep_traits::NA_INTEGER);
1540 }
1541 sexp
1542 }
1543 }
1544
1545 #[inline]
1546 unsafe fn into_sexp_unchecked(self) -> crate::ffi::SEXP {
1547 unsafe {
1548 let (sexp, dst) = crate::into_r::alloc_r_vector_unchecked::<i32>(self.0.len());
1549 for (slot, val) in dst.iter_mut().zip(self.0) {
1550 *slot = val.unwrap_or(crate::altrep_traits::NA_INTEGER);
1551 }
1552 sexp
1553 }
1554 }
1555}
1556// endregion