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//!
62//! ## A note on the `Collect*` adapters (naming-convention boundary)
63//!
64//! This module also exposes [`Collect`], [`CollectStrings`], [`CollectNA`], and
65//! [`CollectNAInt`]. These are representation-forcing `IntoR` wrappers in the same
66//! spirit as the `As*` family, but they deliberately **do not** carry the `As*`
67//! prefix. The distinction is structural: the `As*` wrappers wrap a *finished value*
68//! `T` and re-route its conversion (`AsList<T>`, `AsExternalPtr<T>`, `AsRNative<T>`),
69//! whereas the `Collect*` wrappers wrap an *iterator* `I: ExactSizeIterator` and
70//! materialize it directly into a freshly allocated R vector. The `As*`-of-`T` shape
71//! ("convert this value *as* a list / pointer / native scalar") does not describe what
72//! these do — `Collect` names the operation (drain an iterator into an R vector),
73//! which is the more accurate verb. This divergence is intentional and tracked; see
74//! the conversion control-surface analysis (`analysis/conversion-control-surface-2026-06-07.md`,
75//! §3.4 / §4.5) and issue #871.
76
77use crate::RNativeType;
78use crate::externalptr::{ExternalPtr, IntoExternalPtr};
79use crate::into_r::IntoR;
80use crate::list::{IntoList, List};
81use crate::named_vector::AtomicElement;
82
83/// Wrap a value and convert it to an R list via [`IntoList`] when returned from Rust.
84///
85/// Use this wrapper when you want to convert a single value to an R list without
86/// making that the default behavior for the type.
87///
88/// # Example
89///
90/// ```ignore
91/// #[derive(IntoList)]
92/// struct Point { x: f64, y: f64 }
93///
94/// #[miniextendr]
95/// fn make_point() -> AsList<Point> {
96/// AsList(Point { x: 1.0, y: 2.0 })
97/// }
98/// // In R: make_point() returns list(x = 1.0, y = 2.0)
99/// ```
100#[derive(Debug, Clone, Copy)]
101pub struct AsList<T: IntoList>(pub T);
102
103impl<T: IntoList> From<T> for AsList<T> {
104 fn from(value: T) -> Self {
105 AsList(value)
106 }
107}
108
109impl<T: IntoList> IntoR for AsList<T> {
110 type Error = std::convert::Infallible;
111
112 #[inline]
113 fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
114 Ok(self.into_sexp())
115 }
116
117 #[inline]
118 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
119 self.try_into_sexp()
120 }
121
122 #[inline]
123 fn into_sexp(self) -> crate::SEXP {
124 self.0.into_list().into_sexp()
125 }
126}
127
128// The historical public `convert::IntoDataFrame` (`-> List`) is retired. Its row→List engine
129// now lives in `crate::dataframe::ColumnSource` (an internal `#[doc(hidden)]` trait that the
130// enum-flatten codegen and the new public `dataframe::IntoDataFrame` both delegate to). The
131// public verb surface is `dataframe::{IntoDataFrame, FromDataFrame}` (returning
132// `Result<BuiltDataFrame, DataFrameError>`), which mirrors `IntoR` / `TryFromSexp`.
133pub use crate::dataframe::ColumnSource;
134
135// region: Column gather/scatter helpers
136
137/// Scatter a typed column SEXP from a dense inner data frame into a new
138/// SEXP of length `n_rows`, placing `NA`/`NULL` at rows not in `present_idx`.
139///
140/// This is called by `DataFrameRow`-derived enum code to flatten struct-typed
141/// variant fields into prefixed columns of the parent data frame.
142///
143/// The output type mirrors the input:
144/// - REALSXP → REALSXP (NA_real_ fill)
145/// - INTSXP → INTSXP (NA_integer_ fill)
146/// - LGLSXP → LGLSXP (NA_logical fill)
147/// - RAWSXP → RAWSXP (`0x00` fill — R raw has no NA)
148/// - CPLXSXP → CPLXSXP (NA complex fill: both parts NA_real_)
149/// - STRSXP → STRSXP (NA_character_ fill)
150/// - VECSXP → VECSXP (R_NilValue fill)
151/// - anything else → VECSXP (R_NilValue fill)
152///
153/// Contiguous primitive columns (real/integer/logical/raw/complex) scatter as a
154/// flat slice write via [`scatter_native`] — the NA-filled inverse of
155/// [`gather_native`]. String and list columns stay element-by-element because
156/// they are write-barriered arrays of `SEXP` pointers, not flat buffers.
157///
158/// # Safety
159///
160/// Must be called on the R main thread. `src` must be a valid SEXP of length
161/// `>= present_idx.len()`. `n_rows` must equal the total row count of the
162/// parent data frame.
163#[doc(hidden)]
164pub unsafe fn scatter_column(
165 src: crate::SEXP,
166 present_idx: &[usize],
167 n_rows: usize,
168) -> crate::SEXP {
169 // SAFETY: caller guarantees R main thread; src is valid; n_rows is correct.
170 #[allow(unused_unsafe)]
171 unsafe {
172 use crate::{RLogical, Rcomplex, SEXPTYPE, SexpExt as _};
173
174 match src.type_of() {
175 SEXPTYPE::REALSXP => scatter_native::<f64>(src, present_idx, n_rows),
176 SEXPTYPE::INTSXP => scatter_native::<i32>(src, present_idx, n_rows),
177 SEXPTYPE::LGLSXP => scatter_native::<RLogical>(src, present_idx, n_rows),
178 SEXPTYPE::RAWSXP => scatter_native::<u8>(src, present_idx, n_rows),
179 SEXPTYPE::CPLXSXP => scatter_native::<Rcomplex>(src, present_idx, n_rows),
180 SEXPTYPE::STRSXP => {
181 let out = crate::sys::Rf_allocVector(SEXPTYPE::STRSXP, n_rows as isize);
182 // Write-barriered CHARSXP-pointer array — fill NA_character_, then
183 // scatter present cells element-by-element.
184 for i in 0..(n_rows as isize) {
185 out.set_string_elt(i, crate::SEXP::na_string());
186 }
187 for (pi, &row_i) in present_idx.iter().enumerate() {
188 out.set_string_elt(row_i as isize, src.string_elt(pi as isize));
189 }
190 out
191 }
192 SEXPTYPE::VECSXP => {
193 let out = crate::sys::Rf_allocVector(SEXPTYPE::VECSXP, n_rows as isize);
194 // Write-barriered SEXP-pointer array. R_NilValue fill is automatic
195 // (Rf_allocVector zero-initialises VECSXP slots).
196 for (pi, &row_i) in present_idx.iter().enumerate() {
197 out.set_vector_elt(row_i as isize, src.vector_elt(pi as isize));
198 }
199 out
200 }
201 _ => {
202 // Unknown/unsupported type — produce a VECSXP list-column.
203 // Cells for absent rows remain R_NilValue.
204 let out = crate::sys::Rf_allocVector(SEXPTYPE::VECSXP, n_rows as isize);
205 for (pi, &row_i) in present_idx.iter().enumerate() {
206 out.set_vector_elt(row_i as isize, src.vector_elt(pi as isize));
207 }
208 out
209 }
210 }
211 }
212}
213
214/// Scatter a dense typed column into a fresh SEXP of length `n_rows`, placing the
215/// `j`-th source value at row `present_idx[j]` and the per-type NA sentinel
216/// ([`RNativeType::R_NA`]) at every absent row.
217///
218/// The sparse-placing inverse of [`gather_native`]. Generic over the R element
219/// type: `f64`/`i32`/`RLogical`/`u8`/`Rcomplex` each resolve to the correct
220/// storage via their `RNativeType` impl, so the whole copy is a flat slice write
221/// with no per-element FFI call. Raw (`u8`) has no R NA, so absent positions
222/// become `0x00`.
223///
224/// # Safety
225///
226/// R main thread; `src` must be a `T::SEXP_TYPE` vector with at least
227/// `present_idx.len()` elements, and every index in `present_idx` must be
228/// `< n_rows`. The returned SEXP is unprotected (see [`scatter_column`]). No
229/// allocation occurs between allocating `out` and filling it, so `out` cannot be
230/// reaped mid-scatter.
231#[inline]
232unsafe fn scatter_native<T: RNativeType + Copy>(
233 src: crate::SEXP,
234 present_idx: &[usize],
235 n_rows: usize,
236) -> crate::SEXP {
237 unsafe {
238 use crate::SexpExt as _;
239 let src_vals: &[T] = src.as_slice::<T>();
240 let out = crate::sys::Rf_allocVector(T::SEXP_TYPE, n_rows as isize);
241 let out_vals: &mut [T] = out.as_mut_slice::<T>();
242 // NA-fill the whole output, then place present values. No allocation
243 // between alloc and fill — `out` is GC-safe.
244 out_vals.fill(T::R_NA);
245 for (pi, &row_i) in present_idx.iter().enumerate() {
246 out_vals[row_i] = src_vals[pi];
247 }
248 out
249 }
250}
251
252/// Gather the rows at `idx` (0-based, in order) from a contiguous primitive
253/// column into a fresh dense vector, where `out[j] = src[idx[j]]`.
254///
255/// Generic over the R element type: `f64`/`i32`/`RLogical`/`u8`/`Rcomplex` each
256/// resolve to the correct `REAL`/`INTEGER`/`LOGICAL`/`RAW`/`COMPLEX` storage via
257/// their `RNativeType` impl, so the copy is a plain slice gather with no
258/// per-element FFI call.
259///
260/// # Safety
261///
262/// R main thread; `src` must be a `T::SEXP_TYPE` vector and every index in `idx`
263/// must be `< xlength(src)`. The returned SEXP is unprotected (see
264/// [`gather_column`]). No allocation occurs between allocating `out` and filling
265/// it, so `out` cannot be reaped mid-gather.
266#[inline]
267unsafe fn gather_native<T: RNativeType + Copy>(src: crate::SEXP, idx: &[usize]) -> crate::SEXP {
268 unsafe {
269 use crate::SexpExt as _;
270 let src_vals: &[T] = src.as_slice::<T>();
271 let out = crate::sys::Rf_allocVector(T::SEXP_TYPE, idx.len() as isize);
272 let out_vals: &mut [T] = out.as_mut_slice::<T>();
273 for (dst, &row_i) in out_vals.iter_mut().zip(idx) {
274 *dst = src_vals[row_i];
275 }
276 out
277 }
278}
279
280/// Gather the rows at `idx` (0-based, in order) out of a typed column SEXP into a
281/// new dense SEXP of length `idx.len()`, where `out[j] = src[idx[j]]`.
282///
283/// The row-selecting inverse of [`scatter_column`]: where `scatter_column`
284/// places a dense column's values at sparse positions, `gather_column` pulls a
285/// dense subset out of a column by row index. Used by `DataFrame::select_rows`
286/// to densify a flattened sub-frame before the enum reader recurses.
287///
288/// Contiguous primitive columns (real/integer/logical/raw/complex) are copied as
289/// a slice gather via [`gather_native`]; string and list columns are copied
290/// element-by-element because they are write-barriered arrays of `SEXP` pointers,
291/// not flat buffers. The output type mirrors the input; any other type falls back
292/// to a logical `NA` column (normal `data.frame` columns never reach it).
293///
294/// Column attributes (`class`/`levels` for factor / Date / POSIXct) are **not**
295/// copied — the caller restores those after rooting the gathered column.
296///
297/// # Safety
298///
299/// Must be called on the R main thread. `src` must be a valid SEXP and every
300/// index in `idx` must be `< xlength(src)`. The returned SEXP is unprotected;
301/// the caller must root it (e.g. via `SET_VECTOR_ELT` into a protected list)
302/// before performing any allocation.
303#[doc(hidden)]
304pub unsafe fn gather_column(src: crate::SEXP, idx: &[usize]) -> crate::SEXP {
305 // SAFETY: caller guarantees R main thread, a valid `src`, and in-range indices.
306 #[allow(unused_unsafe)]
307 unsafe {
308 use crate::{RLogical, Rcomplex, SEXPTYPE, SexpExt as _};
309
310 match src.type_of() {
311 SEXPTYPE::REALSXP => gather_native::<f64>(src, idx),
312 SEXPTYPE::INTSXP => gather_native::<i32>(src, idx),
313 SEXPTYPE::LGLSXP => gather_native::<RLogical>(src, idx),
314 SEXPTYPE::RAWSXP => gather_native::<u8>(src, idx),
315 SEXPTYPE::CPLXSXP => gather_native::<Rcomplex>(src, idx),
316 SEXPTYPE::STRSXP => {
317 // Write-barriered CHARSXP-pointer array — copy element-by-element.
318 let out = crate::sys::Rf_allocVector(SEXPTYPE::STRSXP, idx.len() as isize);
319 for (j, &row_i) in idx.iter().enumerate() {
320 out.set_string_elt(j as isize, src.string_elt(row_i as isize));
321 }
322 out
323 }
324 SEXPTYPE::VECSXP => {
325 // Write-barriered SEXP-pointer array (list-column) — element-by-element.
326 let out = crate::sys::Rf_allocVector(SEXPTYPE::VECSXP, idx.len() as isize);
327 for (j, &row_i) in idx.iter().enumerate() {
328 out.set_vector_elt(j as isize, src.vector_elt(row_i as isize));
329 }
330 out
331 }
332 _ => {
333 // Unknown SEXPTYPE: fall back to a logical NA column of the right length.
334 let out = crate::sys::Rf_allocVector(SEXPTYPE::LGLSXP, idx.len() as isize);
335 let na_vals: &mut [RLogical] = out.as_mut_slice::<RLogical>();
336 na_vals.fill(RLogical::NA);
337 out
338 }
339 }
340 }
341}
342// endregion
343
344/// Wrap a value and convert it to an R external pointer when returned from Rust.
345///
346/// Use this wrapper when you want to return a Rust value as an opaque pointer
347/// that R code can pass back to Rust functions later.
348///
349/// # Example
350///
351/// ```ignore
352/// struct Connection { handle: u64 }
353///
354/// impl IntoExternalPtr for Connection { /* ... */ }
355///
356/// #[miniextendr]
357/// fn open_connection(path: &str) -> AsExternalPtr<Connection> {
358/// AsExternalPtr(Connection { handle: 42 })
359/// }
360/// // In R: open_connection("foo") returns an external pointer
361/// ```
362#[derive(Debug, Clone, Copy)]
363pub struct AsExternalPtr<T: IntoExternalPtr>(pub T);
364
365impl<T: IntoExternalPtr> From<T> for AsExternalPtr<T> {
366 fn from(value: T) -> Self {
367 AsExternalPtr(value)
368 }
369}
370
371impl<T: IntoExternalPtr> IntoR for AsExternalPtr<T> {
372 type Error = std::convert::Infallible;
373
374 #[inline]
375 fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
376 Ok(self.into_sexp())
377 }
378
379 #[inline]
380 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
381 self.try_into_sexp()
382 }
383
384 #[inline]
385 fn into_sexp(self) -> crate::SEXP {
386 ExternalPtr::new(self.0).into_sexp()
387 }
388}
389
390/// Wrap a scalar [`RNativeType`] and force native R vector conversion.
391///
392/// This creates a length-1 R vector containing the scalar value. Use this when
393/// you want to ensure a value is converted to its native R representation (e.g.,
394/// `i32` → integer vector, `f64` → numeric vector) rather than another path
395/// like `IntoExternalPtr`.
396///
397/// # Example
398///
399/// ```ignore
400/// #[derive(Clone, Copy, RNativeType)]
401/// struct Meters(f64);
402///
403/// #[miniextendr]
404/// fn distance() -> AsRNative<Meters> {
405/// AsRNative(Meters(42.5))
406/// }
407/// // In R: distance() returns 42.5 (numeric vector of length 1)
408/// ```
409///
410/// # Performance
411///
412/// This wrapper directly allocates an R vector and writes the value,
413/// avoiding intermediate Rust allocations.
414#[derive(Debug, Clone, Copy)]
415pub struct AsRNative<T: RNativeType>(pub T);
416
417impl<T: RNativeType> From<T> for AsRNative<T> {
418 fn from(value: T) -> Self {
419 AsRNative(value)
420 }
421}
422
423impl<T: RNativeType> IntoR for AsRNative<T> {
424 type Error = std::convert::Infallible;
425
426 #[inline]
427 fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
428 Ok(self.into_sexp())
429 }
430
431 #[inline]
432 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
433 Ok(unsafe { self.into_sexp_unchecked() })
434 }
435
436 #[inline]
437 fn into_sexp(self) -> crate::SEXP {
438 // Directly allocate a length-1 R vector and write the scalar value.
439 // This avoids the intermediate Rust Vec allocation.
440 unsafe {
441 let sexp = crate::sys::Rf_allocVector(T::SEXP_TYPE, 1);
442 let ptr = T::dataptr_mut(sexp);
443 std::ptr::write(ptr, self.0);
444 sexp
445 }
446 }
447
448 #[inline]
449 unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
450 unsafe {
451 let sexp = crate::sys::Rf_allocVector_unchecked(T::SEXP_TYPE, 1);
452 let ptr = T::dataptr_mut(sexp);
453 std::ptr::write(ptr, self.0);
454 sexp
455 }
456 }
457}
458// endregion
459
460// region: DataFrame / Vctrs representation wrappers
461
462/// Wrap a value and convert it to an R `data.frame` via [`IntoDataFrame`](crate::dataframe::IntoDataFrame) when returned.
463///
464/// Use this at a call site to force a single return value into a data.frame without making
465/// that the type's default representation (for the always-a-data.frame default, use
466/// `#[derive(PreferDataFrame)]` / `#[miniextendr(dataframe)]`). The inner `T` is typically a
467/// `Vec<Row>` where `Row` derives [`DataFrameRow`](crate::markers::DataFrameRow).
468///
469/// A failed conversion ([`DataFrameError`](crate::dataframe::DataFrameError)) surfaces in R as
470/// an error condition.
471///
472/// # Example
473///
474/// ```ignore
475/// #[derive(DataFrameRow)]
476/// struct Point { x: f64, y: f64 }
477///
478/// #[miniextendr]
479/// fn grid() -> AsDataFrame<Vec<Point>> {
480/// AsDataFrame(vec![Point { x: 0.0, y: 0.0 }, Point { x: 1.0, y: 1.0 }])
481/// }
482/// // In R: grid() returns a data.frame with columns x, y
483/// ```
484#[derive(Debug, Clone)]
485pub struct AsDataFrame<T: crate::dataframe::IntoDataFrame>(pub T);
486
487impl<T: crate::dataframe::IntoDataFrame> From<T> for AsDataFrame<T> {
488 fn from(value: T) -> Self {
489 AsDataFrame(value)
490 }
491}
492
493impl<T: crate::dataframe::IntoDataFrame> IntoR for AsDataFrame<T> {
494 type Error = crate::dataframe::DataFrameError;
495
496 #[inline]
497 fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
498 Ok(self.0.into_dataframe()?.into_sexp())
499 }
500
501 #[inline]
502 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
503 self.try_into_sexp()
504 }
505}
506
507/// Wrap a value and convert it to a **vctrs** S3 vector via [`IntoVctrs`](crate::vctrs::IntoVctrs)
508/// when returned.
509///
510/// Use this at a call site to return a `#[derive(Vctrs)]` type as its R vctrs object without the
511/// manual `value.into_vctrs().map_err(...)` boilerplate. For a type that should *always* convert
512/// this way, use `#[derive(Vctrs, PreferVctrs)]` instead.
513///
514/// A failed build ([`VctrsBuildError`](crate::vctrs::VctrsBuildError)) surfaces in R as an error
515/// condition.
516///
517/// # Example
518///
519/// ```ignore
520/// #[derive(Vctrs)]
521/// #[vctrs(class = "percent", base = "double")]
522/// struct Percent { #[vctrs(data)] values: Vec<f64> }
523///
524/// #[miniextendr]
525/// fn percent(x: Vec<f64>) -> AsVctrs<Percent> {
526/// AsVctrs(Percent { values: x })
527/// }
528/// ```
529#[cfg(feature = "vctrs")]
530#[derive(Debug, Clone)]
531pub struct AsVctrs<T: crate::vctrs::IntoVctrs>(pub T);
532
533#[cfg(feature = "vctrs")]
534impl<T: crate::vctrs::IntoVctrs> From<T> for AsVctrs<T> {
535 fn from(value: T) -> Self {
536 AsVctrs(value)
537 }
538}
539
540#[cfg(feature = "vctrs")]
541impl<T: crate::vctrs::IntoVctrs> IntoR for AsVctrs<T> {
542 type Error = crate::vctrs::VctrsBuildError;
543
544 #[inline]
545 fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
546 self.0.into_vctrs()
547 }
548
549 #[inline]
550 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
551 self.try_into_sexp()
552 }
553}
554// endregion
555
556// region: Named pair wrappers
557
558/// Wrap a tuple pair collection and convert it to a **named R list** (VECSXP).
559///
560/// Preserves insertion order and allows duplicate names (sequence semantics).
561///
562/// # Supported input types
563///
564/// | Input | Bounds |
565/// |-------|--------|
566/// | `Vec<(K, V)>` | `K: AsRef<str>`, `V: IntoR` |
567/// | `[(K, V); N]` | `K: AsRef<str>`, `V: IntoR` |
568/// | `&[(K, V)]` | `K: AsRef<str>`, `V: Clone + IntoR` |
569///
570/// # GC safety — use *typed* values, never raw `SEXP` (#1030)
571///
572/// Prefer typed value types (`i32`, `String`, `Vec<T>`, …) over raw
573/// [`crate::SEXP`]. With a typed `V`, each value's `into_sexp()` runs *inside*
574/// [`AsNamedList::into_sexp`][IntoR::into_sexp], immediately before the value is
575/// protected and placed into the list — no allocation ever fires while a sibling
576/// value sits unprotected.
577///
578/// A raw `SEXP` value (`V = SEXP`) is **GC-fragile and must not be used**. The
579/// caller has to build the `SEXP` values *in an earlier frame* (each `into_sexp()`
580/// allocates) and then hand them to `AsNamedList`; the deferred `from_raw_pairs`
581/// runs later, in the generated wrapper, so there is **no call site at which a
582/// `ProtectScope` can span both value construction and the deferred list build**.
583/// The internal protection added in `into_sexp` (below) closes the *receipt →
584/// `from_raw_pairs`* window but cannot retroactively protect values that were
585/// already built and unrooted in the caller — that *construction* window is the
586/// latent use-after-free. Stable Rust cannot exclude one concrete type from a
587/// `V: IntoR` blanket, so this is enforced by convention (and the MXL302 lint on
588/// the `into_sexp()`-in-`vec!` idiom) rather than the type system; passing typed
589/// values sidesteps the hazard entirely.
590///
591/// # Example
592///
593/// ```ignore
594/// #[miniextendr]
595/// fn make_config() -> AsNamedList<Vec<(String, i32)>> {
596/// AsNamedList(vec![
597/// ("width".into(), 100),
598/// ("height".into(), 200),
599/// ])
600/// }
601/// // In R: make_config() returns list(width = 100L, height = 200L)
602/// ```
603#[derive(Debug, Clone)]
604pub struct AsNamedList<T>(pub T);
605
606impl<T> From<T> for AsNamedList<T> {
607 fn from(value: T) -> Self {
608 AsNamedList(value)
609 }
610}
611
612impl<K: AsRef<str>, V: IntoR> IntoR for AsNamedList<Vec<(K, V)>> {
613 type Error = std::convert::Infallible;
614
615 fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
616 Ok(self.into_sexp())
617 }
618
619 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
620 self.try_into_sexp()
621 }
622
623 fn into_sexp(self) -> crate::SEXP {
624 // SAFETY: `into_sexp` for `#[miniextendr]` return values runs on the R
625 // main thread. Each value's `into_sexp()` is built and immediately
626 // `protect_raw`-rooted before the next value's allocation, mirroring the
627 // `#[derive(IntoList)]` codegen — no sibling sits unprotected across an
628 // allocation. The scope drops after `from_raw_pairs` returns. (#1030)
629 unsafe {
630 let scope = crate::gc_protect::ProtectScope::new();
631 let pairs: Vec<(K, crate::SEXP)> = self
632 .0
633 .into_iter()
634 .map(|(k, v)| (k, scope.protect_raw(v.into_sexp())))
635 .collect();
636 List::from_raw_pairs(pairs).into_sexp()
637 }
638 }
639}
640
641impl<K: AsRef<str>, V: IntoR, const N: usize> IntoR for AsNamedList<[(K, V); N]> {
642 type Error = std::convert::Infallible;
643
644 fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
645 Ok(self.into_sexp())
646 }
647
648 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
649 self.try_into_sexp()
650 }
651
652 fn into_sexp(self) -> crate::SEXP {
653 // SAFETY: see the `Vec<(K, V)>` impl above — main thread, each value
654 // rooted before the next allocates. (#1030)
655 unsafe {
656 let scope = crate::gc_protect::ProtectScope::new();
657 let pairs: Vec<(K, crate::SEXP)> = self
658 .0
659 .into_iter()
660 .map(|(k, v)| (k, scope.protect_raw(v.into_sexp())))
661 .collect();
662 List::from_raw_pairs(pairs).into_sexp()
663 }
664 }
665}
666
667impl<K: AsRef<str>, V: Clone + IntoR> IntoR for AsNamedList<&[(K, V)]> {
668 type Error = std::convert::Infallible;
669
670 fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
671 Ok(self.into_sexp())
672 }
673
674 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
675 self.try_into_sexp()
676 }
677
678 fn into_sexp(self) -> crate::SEXP {
679 // SAFETY: see the `Vec<(K, V)>` impl above — main thread, each value
680 // rooted before the next allocates. (#1030)
681 unsafe {
682 let scope = crate::gc_protect::ProtectScope::new();
683 let pairs: Vec<(&K, crate::SEXP)> = self
684 .0
685 .iter()
686 .map(|(k, v)| (k, scope.protect_raw(v.clone().into_sexp())))
687 .collect();
688 List::from_raw_pairs(pairs).into_sexp()
689 }
690 }
691}
692
693/// Wrap a tuple pair collection and convert it to a **named atomic R vector**
694/// (INTSXP, REALSXP, LGLSXP, RAWSXP, or STRSXP).
695///
696/// Preserves insertion order and allows duplicate names (sequence semantics).
697/// Values must be homogeneous and implement [`AtomicElement`].
698///
699/// # Supported input types
700///
701/// | Input | Bounds |
702/// |-------|--------|
703/// | `Vec<(K, V)>` | `K: AsRef<str>`, `V: AtomicElement` |
704/// | `[(K, V); N]` | `K: AsRef<str>`, `V: AtomicElement` |
705/// | `&[(K, V)]` | `K: AsRef<str>`, `V: Clone + AtomicElement` |
706///
707/// # Example
708///
709/// ```ignore
710/// #[miniextendr]
711/// fn make_scores() -> AsNamedVector<Vec<(&str, f64)>> {
712/// AsNamedVector(vec![("alice", 95.0), ("bob", 87.5)])
713/// }
714/// // In R: make_scores() returns c(alice = 95.0, bob = 87.5)
715/// ```
716#[derive(Debug, Clone)]
717pub struct AsNamedVector<T>(pub T);
718
719impl<T> From<T> for AsNamedVector<T> {
720 fn from(value: T) -> Self {
721 AsNamedVector(value)
722 }
723}
724
725impl<K: AsRef<str>, V: AtomicElement> IntoR for AsNamedVector<Vec<(K, V)>> {
726 type Error = std::convert::Infallible;
727
728 fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
729 Ok(self.into_sexp())
730 }
731
732 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
733 self.try_into_sexp()
734 }
735
736 fn into_sexp(self) -> crate::SEXP {
737 named_vector_from_pairs(self.0)
738 }
739}
740
741impl<K: AsRef<str>, V: AtomicElement, const N: usize> IntoR for AsNamedVector<[(K, V); N]> {
742 type Error = std::convert::Infallible;
743
744 fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
745 Ok(self.into_sexp())
746 }
747
748 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
749 self.try_into_sexp()
750 }
751
752 fn into_sexp(self) -> crate::SEXP {
753 named_vector_from_pairs(self.0)
754 }
755}
756
757impl<K: AsRef<str>, V: Clone + AtomicElement> IntoR for AsNamedVector<&[(K, V)]> {
758 type Error = std::convert::Infallible;
759
760 fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
761 Ok(self.into_sexp())
762 }
763
764 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
765 self.try_into_sexp()
766 }
767
768 fn into_sexp(self) -> crate::SEXP {
769 let (keys, values): (Vec<&K>, Vec<V>) = self.0.iter().map(|(k, v)| (k, v.clone())).unzip();
770 let sexp = V::vec_to_sexp(values);
771 unsafe {
772 let _guard = crate::OwnedProtect::new(sexp);
773 crate::named_vector::set_names_on_sexp(sexp, &keys);
774 }
775 sexp
776 }
777}
778
779/// Shared helper: build a named atomic vector from an owning iterator of (key, value) pairs.
780fn named_vector_from_pairs<K, V>(pairs: impl IntoIterator<Item = (K, V)>) -> crate::SEXP
781where
782 K: AsRef<str>,
783 V: AtomicElement,
784{
785 let (keys, values): (Vec<K>, Vec<V>) = pairs.into_iter().unzip();
786 let sexp = V::vec_to_sexp(values);
787 unsafe {
788 let _guard = crate::OwnedProtect::new(sexp);
789 crate::named_vector::set_names_on_sexp(sexp, &keys);
790 }
791 sexp
792}
793// endregion
794
795// region: Extension traits for ergonomic wrapping
796//
797// These extension traits provide method-style wrapping that works even when
798// the destination type isn't constrained (i.e., `value.wrap_list()` instead
799// of `value.into()` which requires type inference).
800//
801// ```ignore
802// // These all work without type annotations:
803// let wrapped = my_struct.wrap_list();
804// let ptr = my_value.wrap_external_ptr();
805// let native = my_num.wrap_r_native();
806// ```
807
808/// Extension trait for wrapping values as [`AsList`].
809///
810/// This trait is automatically implemented for all types that implement [`IntoList`].
811///
812/// # Example
813///
814/// ```ignore
815/// use miniextendr_api::convert::AsListExt;
816///
817/// #[derive(IntoList)]
818/// struct Point { x: f64, y: f64 }
819///
820/// let point = Point { x: 1.0, y: 2.0 };
821/// let wrapped: AsList<Point> = point.wrap_list();
822/// ```
823pub trait AsListExt: IntoList + Sized {
824 /// Wrap `self` in [`AsList`] for R list conversion.
825 fn wrap_list(self) -> AsList<Self> {
826 AsList(self)
827 }
828}
829
830impl<T: IntoList> AsListExt for T {}
831
832/// Extension trait for wrapping values as [`AsExternalPtr`].
833///
834/// This trait is automatically implemented for all types that implement [`IntoExternalPtr`].
835///
836/// # Example
837///
838/// ```ignore
839/// use miniextendr_api::convert::AsExternalPtrExt;
840///
841/// #[derive(ExternalPtr)]
842/// struct Connection { handle: u64 }
843///
844/// let conn = Connection { handle: 42 };
845/// let wrapped: AsExternalPtr<Connection> = conn.wrap_external_ptr();
846/// ```
847pub trait AsExternalPtrExt: IntoExternalPtr + Sized {
848 /// Wrap `self` in [`AsExternalPtr`] for R external pointer conversion.
849 fn wrap_external_ptr(self) -> AsExternalPtr<Self> {
850 AsExternalPtr(self)
851 }
852}
853
854impl<T: IntoExternalPtr> AsExternalPtrExt for T {}
855
856/// Extension trait for wrapping values as [`AsRNative`].
857///
858/// This trait is automatically implemented for all types that implement [`RNativeType`].
859///
860/// # Example
861///
862/// ```ignore
863/// use miniextendr_api::convert::AsRNativeExt;
864///
865/// let x: f64 = 42.5;
866/// let wrapped: AsRNative<f64> = x.wrap_r_native();
867/// ```
868pub trait AsRNativeExt: RNativeType + Sized {
869 /// Wrap `self` in [`AsRNative`] for native R scalar conversion.
870 fn wrap_r_native(self) -> AsRNative<Self> {
871 AsRNative(self)
872 }
873}
874
875impl<T: RNativeType> AsRNativeExt for T {}
876
877/// Extension trait for wrapping values as [`AsDataFrame`].
878///
879/// Automatically implemented for all `T: IntoDataFrame` (typically `Vec<Row>` where `Row`
880/// derives `DataFrameRow`).
881pub trait AsDataFrameExt: crate::dataframe::IntoDataFrame + Sized {
882 /// Wrap `self` in [`AsDataFrame`] for R data.frame conversion.
883 fn wrap_data_frame(self) -> AsDataFrame<Self> {
884 AsDataFrame(self)
885 }
886}
887
888impl<T: crate::dataframe::IntoDataFrame> AsDataFrameExt for T {}
889
890/// Extension trait for wrapping values as [`AsVctrs`].
891///
892/// Automatically implemented for all `T: IntoVctrs` (typically a `#[derive(Vctrs)]` type).
893#[cfg(feature = "vctrs")]
894pub trait AsVctrsExt: crate::vctrs::IntoVctrs + Sized {
895 /// Wrap `self` in [`AsVctrs`] for R vctrs conversion.
896 fn wrap_vctrs(self) -> AsVctrs<Self> {
897 AsVctrs(self)
898 }
899}
900
901#[cfg(feature = "vctrs")]
902impl<T: crate::vctrs::IntoVctrs> AsVctrsExt for T {}
903
904/// Extension trait for wrapping tuple pair collections as [`AsNamedList`].
905///
906/// # Example
907///
908/// ```ignore
909/// let pairs = vec![("x".to_string(), 1i32), ("y".to_string(), 2i32)];
910/// let wrapped = pairs.wrap_named_list();
911/// ```
912pub trait AsNamedListExt: Sized {
913 /// Wrap `self` in [`AsNamedList`] for named R list conversion.
914 fn wrap_named_list(self) -> AsNamedList<Self> {
915 AsNamedList(self)
916 }
917}
918
919impl<K: AsRef<str>, V: IntoR> AsNamedListExt for Vec<(K, V)> {}
920impl<K: AsRef<str>, V: IntoR, const N: usize> AsNamedListExt for [(K, V); N] {}
921impl<K: AsRef<str>, V: Clone + IntoR> AsNamedListExt for &[(K, V)] {}
922
923/// Extension trait for wrapping tuple pair collections as [`AsNamedVector`].
924///
925/// # Example
926///
927/// ```ignore
928/// let pairs = vec![("alice".to_string(), 95.0f64), ("bob".to_string(), 87.5)];
929/// let wrapped = pairs.wrap_named_vector();
930/// ```
931pub trait AsNamedVectorExt: Sized {
932 /// Wrap `self` in [`AsNamedVector`] for named atomic R vector conversion.
933 fn wrap_named_vector(self) -> AsNamedVector<Self> {
934 AsNamedVector(self)
935 }
936}
937
938impl<K: AsRef<str>, V: AtomicElement> AsNamedVectorExt for Vec<(K, V)> {}
939impl<K: AsRef<str>, V: AtomicElement, const N: usize> AsNamedVectorExt for [(K, V); N] {}
940impl<K: AsRef<str>, V: Clone + AtomicElement> AsNamedVectorExt for &[(K, V)] {}
941// endregion
942
943// region: Display/FromStr trait adapters
944
945/// Wrap a `T: Display` and convert it to an R character scalar.
946///
947/// Any type implementing `std::fmt::Display` can be returned to R as a string
948/// without implementing miniextendr traits.
949///
950/// # Example
951///
952/// ```ignore
953/// use std::net::IpAddr;
954///
955/// #[miniextendr]
956/// fn format_ip(ip: &str) -> AsDisplay<IpAddr> {
957/// AsDisplay(ip.parse().unwrap())
958/// }
959/// // R gets: "192.168.1.1"
960/// ```
961#[derive(Debug, Clone, Copy)]
962pub struct AsDisplay<T>(pub T);
963
964impl<T: std::fmt::Display> IntoR for AsDisplay<T> {
965 type Error = std::convert::Infallible;
966
967 #[inline]
968 fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
969 Ok(self.0.to_string().into_sexp())
970 }
971
972 #[inline]
973 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
974 Ok(unsafe { self.0.to_string().into_sexp_unchecked() })
975 }
976}
977
978/// Wrap a `Vec<T: Display>` and convert it to an R character vector.
979///
980/// # Example
981///
982/// ```ignore
983/// #[miniextendr]
984/// fn format_errors(errors: Vec<std::io::Error>) -> AsDisplayVec<std::io::Error> {
985/// AsDisplayVec(errors)
986/// }
987/// ```
988#[derive(Debug, Clone)]
989pub struct AsDisplayVec<T>(pub Vec<T>);
990
991impl<T: std::fmt::Display> IntoR for AsDisplayVec<T> {
992 type Error = std::convert::Infallible;
993
994 #[inline]
995 fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
996 let strings: Vec<String> = self.0.into_iter().map(|x| x.to_string()).collect();
997 Ok(strings.into_sexp())
998 }
999
1000 #[inline]
1001 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
1002 let strings: Vec<String> = self.0.into_iter().map(|x| x.to_string()).collect();
1003 Ok(unsafe { strings.into_sexp_unchecked() })
1004 }
1005}
1006
1007/// Wrap a parsed `T: FromStr` from an R character scalar.
1008///
1009/// Pass an R character scalar and it will be parsed into `T` via `str::parse()`.
1010///
1011/// # Example
1012///
1013/// ```ignore
1014/// use std::net::IpAddr;
1015///
1016/// #[miniextendr]
1017/// fn check_ip(addr: AsFromStr<IpAddr>) -> bool {
1018/// addr.0.is_loopback()
1019/// }
1020/// // R: check_ip("127.0.0.1") → TRUE
1021/// ```
1022#[derive(Debug, Clone)]
1023pub struct AsFromStr<T>(pub T);
1024
1025impl<T: std::str::FromStr> crate::from_r::TryFromSexp for AsFromStr<T>
1026where
1027 T::Err: std::fmt::Display,
1028{
1029 type Error = crate::from_r::SexpError;
1030
1031 fn try_from_sexp(sexp: crate::SEXP) -> Result<Self, Self::Error> {
1032 let s: &str = crate::from_r::TryFromSexp::try_from_sexp(sexp)?;
1033 let value = s
1034 .parse::<T>()
1035 .map_err(|e| crate::from_r::SexpError::InvalidValue(format!("{e}")))?;
1036 Ok(AsFromStr(value))
1037 }
1038
1039 unsafe fn try_from_sexp_unchecked(sexp: crate::SEXP) -> Result<Self, Self::Error> {
1040 let s: &str = unsafe { crate::from_r::TryFromSexp::try_from_sexp_unchecked(sexp)? };
1041 let value = s
1042 .parse::<T>()
1043 .map_err(|e| crate::from_r::SexpError::InvalidValue(format!("{e}")))?;
1044 Ok(AsFromStr(value))
1045 }
1046}
1047
1048/// Wrap a `Vec<T: FromStr>` parsed from an R character vector.
1049///
1050/// Each element of the R character vector is parsed into `T`.
1051/// All parse errors are collected with their indices.
1052///
1053/// # Example
1054///
1055/// ```ignore
1056/// use std::net::IpAddr;
1057///
1058/// #[miniextendr]
1059/// fn parse_ips(addrs: AsFromStrVec<IpAddr>) -> Vec<bool> {
1060/// addrs.0.into_iter().map(|ip| ip.is_loopback()).collect()
1061/// }
1062/// // R: parse_ips(c("127.0.0.1", "8.8.8.8")) → c(TRUE, FALSE)
1063/// ```
1064#[derive(Debug, Clone)]
1065pub struct AsFromStrVec<T>(pub Vec<T>);
1066
1067impl<T: std::str::FromStr> crate::from_r::TryFromSexp for AsFromStrVec<T>
1068where
1069 T::Err: std::fmt::Display,
1070{
1071 type Error = crate::from_r::SexpError;
1072
1073 fn try_from_sexp(sexp: crate::SEXP) -> Result<Self, Self::Error> {
1074 let strings: Vec<String> = crate::from_r::TryFromSexp::try_from_sexp(sexp)?;
1075 let mut result = Vec::with_capacity(strings.len());
1076 let mut errors = Vec::new();
1077 for (i, s) in strings.iter().enumerate() {
1078 match s.parse::<T>() {
1079 Ok(v) => result.push(v),
1080 Err(e) => errors.push(format!("index {i}: {e}")),
1081 }
1082 }
1083 if errors.is_empty() {
1084 Ok(AsFromStrVec(result))
1085 } else {
1086 Err(crate::from_r::SexpError::InvalidValue(format!(
1087 "parse errors: {}",
1088 errors.join("; ")
1089 )))
1090 }
1091 }
1092
1093 unsafe fn try_from_sexp_unchecked(sexp: crate::SEXP) -> Result<Self, Self::Error> {
1094 let strings: Vec<String> =
1095 unsafe { crate::from_r::TryFromSexp::try_from_sexp_unchecked(sexp)? };
1096 let mut result = Vec::with_capacity(strings.len());
1097 let mut errors = Vec::new();
1098 for (i, s) in strings.iter().enumerate() {
1099 match s.parse::<T>() {
1100 Ok(v) => result.push(v),
1101 Err(e) => errors.push(format!("index {i}: {e}")),
1102 }
1103 }
1104 if errors.is_empty() {
1105 Ok(AsFromStrVec(result))
1106 } else {
1107 Err(crate::from_r::SexpError::InvalidValue(format!(
1108 "parse errors: {}",
1109 errors.join("; ")
1110 )))
1111 }
1112 }
1113}
1114// endregion
1115
1116// region: Collect — zero-allocation iterator-to-R-vector adapters
1117//
1118// Naming-convention exemption: the four `Collect*` types below are
1119// representation-forcing `IntoR` wrappers, like the `As*` family above, but they
1120// intentionally diverge from the `As*` prefix. `As*` wraps a finished value `T`
1121// and re-routes its conversion; `Collect*` wraps an *iterator*
1122// (`I: ExactSizeIterator`) and drains it straight into a freshly allocated R
1123// vector. The `As*`-of-`T` shape does not describe an iterator adapter, so
1124// `Collect` (the verb for the operation) is the accurate name. See the module-level
1125// docs, `analysis/conversion-control-surface-2026-06-07.md` (§3.4 / §4.5), and #871.
1126
1127/// Write an `ExactSizeIterator` of native R types directly into an R vector.
1128///
1129/// Skips the intermediate `Vec` allocation — the R vector is allocated once
1130/// and the iterator writes directly into it.
1131///
1132/// Requires `ExactSizeIterator` because R vectors must know their length
1133/// at allocation time.
1134///
1135/// # Naming
1136///
1137/// `Collect` is in the representation-forcing wrapper family but does not take the
1138/// `As*` prefix used by [`AsList`] / [`AsExternalPtr`] / [`AsRNative`]: those wrap a
1139/// finished value `T`, whereas `Collect` wraps an *iterator* and materializes it into
1140/// an R vector. The divergence is intentional — see the module docs and #871.
1141///
1142/// # Example
1143///
1144/// ```ignore
1145/// #[miniextendr]
1146/// fn sines(n: i32) -> Collect<impl ExactSizeIterator<Item = f64>> {
1147/// Collect((0..n).map(|i| (i as f64).sin()))
1148/// }
1149/// ```
1150pub struct Collect<I>(pub I);
1151
1152impl<I, T> IntoR for Collect<I>
1153where
1154 I: ExactSizeIterator<Item = T>,
1155 T: crate::RNativeType,
1156{
1157 type Error = std::convert::Infallible;
1158
1159 #[inline]
1160 fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
1161 Ok(self.into_sexp())
1162 }
1163
1164 #[inline]
1165 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
1166 Ok(unsafe { self.into_sexp_unchecked() })
1167 }
1168
1169 #[inline]
1170 fn into_sexp(self) -> crate::SEXP {
1171 unsafe {
1172 let (sexp, dst) = crate::into_r::alloc_r_vector::<T>(self.0.len());
1173 for (slot, val) in dst.iter_mut().zip(self.0) {
1174 *slot = val;
1175 }
1176 sexp
1177 }
1178 }
1179
1180 #[inline]
1181 unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
1182 unsafe {
1183 let (sexp, dst) = crate::into_r::alloc_r_vector_unchecked::<T>(self.0.len());
1184 for (slot, val) in dst.iter_mut().zip(self.0) {
1185 *slot = val;
1186 }
1187 sexp
1188 }
1189 }
1190}
1191
1192/// Write an `ExactSizeIterator` of `String` directly into an R character vector.
1193///
1194/// Strings require per-element CHARSXP allocation (no bulk `copy_from_slice`),
1195/// so this is a separate type from [`Collect`]. Like [`Collect`], it is an
1196/// iterator adapter and is exempt from the `As*` naming convention (see #871).
1197///
1198/// # Example
1199///
1200/// ```ignore
1201/// #[miniextendr]
1202/// fn upper(words: Vec<String>) -> CollectStrings<impl ExactSizeIterator<Item = String>> {
1203/// CollectStrings(words.into_iter().map(|w| w.to_uppercase()))
1204/// }
1205/// ```
1206pub struct CollectStrings<I>(pub I);
1207
1208impl<I> IntoR for CollectStrings<I>
1209where
1210 I: ExactSizeIterator<Item = String>,
1211{
1212 type Error = std::convert::Infallible;
1213
1214 #[inline]
1215 fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
1216 // Collect String refs for str_iter_to_strsxp.
1217 let strings: Vec<String> = self.0.collect();
1218 Ok(crate::into_r::str_iter_to_strsxp(
1219 strings.iter().map(|s| s.as_str()),
1220 ))
1221 }
1222
1223 #[inline]
1224 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
1225 let strings: Vec<String> = self.0.collect();
1226 Ok(unsafe {
1227 crate::into_r::str_iter_to_strsxp_unchecked(strings.iter().map(|s| s.as_str()))
1228 })
1229 }
1230}
1231
1232/// Write an `ExactSizeIterator` of `Option<T>` directly into an R vector with NA support.
1233///
1234/// `None` values become `NA` in R. Works for `f64` and `i32`.
1235///
1236/// Like [`Collect`], this is an iterator adapter and is exempt from the `As*`
1237/// naming convention (see #871).
1238///
1239/// # Example
1240///
1241/// ```ignore
1242/// #[miniextendr]
1243/// fn with_gaps(n: i32) -> CollectNA<impl ExactSizeIterator<Item = Option<f64>>> {
1244/// CollectNA((0..n).map(|i| if i % 3 == 0 { None } else { Some(i as f64) }))
1245/// }
1246/// ```
1247pub struct CollectNA<I>(pub I);
1248
1249impl<I> IntoR for CollectNA<I>
1250where
1251 I: ExactSizeIterator<Item = Option<f64>>,
1252{
1253 type Error = std::convert::Infallible;
1254
1255 #[inline]
1256 fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
1257 Ok(self.into_sexp())
1258 }
1259
1260 #[inline]
1261 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
1262 Ok(unsafe { self.into_sexp_unchecked() })
1263 }
1264
1265 #[inline]
1266 fn into_sexp(self) -> crate::SEXP {
1267 unsafe {
1268 let (sexp, dst) = crate::into_r::alloc_r_vector::<f64>(self.0.len());
1269 for (slot, val) in dst.iter_mut().zip(self.0) {
1270 *slot = val.unwrap_or(crate::altrep_traits::NA_REAL);
1271 }
1272 sexp
1273 }
1274 }
1275
1276 #[inline]
1277 unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
1278 unsafe {
1279 let (sexp, dst) = crate::into_r::alloc_r_vector_unchecked::<f64>(self.0.len());
1280 for (slot, val) in dst.iter_mut().zip(self.0) {
1281 *slot = val.unwrap_or(crate::altrep_traits::NA_REAL);
1282 }
1283 sexp
1284 }
1285 }
1286}
1287
1288/// Write an `ExactSizeIterator` of `Option<i32>` directly into an R integer vector with NA.
1289///
1290/// Like [`Collect`], this is an iterator adapter and is exempt from the `As*`
1291/// naming convention (see #871).
1292pub struct CollectNAInt<I>(pub I);
1293
1294impl<I> IntoR for CollectNAInt<I>
1295where
1296 I: ExactSizeIterator<Item = Option<i32>>,
1297{
1298 type Error = std::convert::Infallible;
1299
1300 #[inline]
1301 fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
1302 Ok(self.into_sexp())
1303 }
1304
1305 #[inline]
1306 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
1307 Ok(unsafe { self.into_sexp_unchecked() })
1308 }
1309
1310 #[inline]
1311 fn into_sexp(self) -> crate::SEXP {
1312 unsafe {
1313 let (sexp, dst) = crate::into_r::alloc_r_vector::<i32>(self.0.len());
1314 for (slot, val) in dst.iter_mut().zip(self.0) {
1315 *slot = val.unwrap_or(crate::altrep_traits::NA_INTEGER);
1316 }
1317 sexp
1318 }
1319 }
1320
1321 #[inline]
1322 unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
1323 unsafe {
1324 let (sexp, dst) = crate::into_r::alloc_r_vector_unchecked::<i32>(self.0.len());
1325 for (slot, val) in dst.iter_mut().zip(self.0) {
1326 *slot = val.unwrap_or(crate::altrep_traits::NA_INTEGER);
1327 }
1328 sexp
1329 }
1330 }
1331}
1332// endregion