1#![allow(rustdoc::private_intra_doc_links)]
2//! Thin wrapper around R list (`VECSXP`).
3//!
4//! Provides safe construction from Rust values and typed extraction.
5//!
6//! # Submodules
7//!
8//! | Module | Contents |
9//! |--------|----------|
10//! | [`accumulator`] | `ListAccumulator` — dynamic list construction with bounded protect stack |
11//! | [`named`] | `NamedList` — O(1) name-indexed access via `HashMap` index |
12//!
13//! # Core Types
14//!
15//! - [`List`] — owned handle to an R list (VECSXP)
16//! - [`ListMut`] — mutable view for in-place element replacement
17//! - [`ListBuilder`] — fixed-size batch construction
18//! - [`NamedListBuilder`] — owned, GC-rooted named lists and data frames
19//! - [`BuiltList`] — the builder's rooted plain-list finish
20//! - [`IntoList`] / [`TryFromList`] — conversion traits
2122mod named_builder;
23pub use named_builder::{BuiltList, NamedListBuilder};
2425use crate::SEXPTYPE::{NILSXP, STRSXP, VECSXP};
26use crate::from_r::{
27ElementCheck, SexpError, SexpLengthError, SexpTypeError, TryFromSexp, map_vecsxp_batched,
28no_element_check,
29};
30use crate::gc_protect::OwnedProtect;
31use crate::into_r::IntoR;
32use crate::sys::{self};
33use crate::{SEXP, SexpExt};
34use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
35use std::hash::Hash;
3637/// Owned handle to an R list (`VECSXP`).
38///
39/// # Examples
40///
41/// ```no_run
42/// use miniextendr_api::list::List;
43///
44/// let list = List::from_values(vec![1i32, 2, 3]);
45/// assert_eq!(list.len(), 3);
46/// let first: Option<i32> = list.get_index(0);
47/// ```
48#[derive(#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for List { }
#[automatically_derived]
impl ::core::clone::Clone for List {
#[inline]
fn clone(&self) -> Self {
let _: ::core::clone::AssertParamIsClone<SEXP>;
*self
}
}Clone, #[automatically_derived]
impl ::core::marker::Copy for List { }Copy, #[automatically_derived]
impl ::core::fmt::Debug for List {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_tuple_field1_finish(f, "List", &&self.0)
}
}Debug)]
49pub struct List(SEXP);
5051/// Mutable view of an R list (`VECSXP`).
52///
53/// This is a wrapper type instead of `&mut [SEXP]` to avoid exposing a raw slice
54/// that could become invalid if list elements are replaced with `NULL`.
55#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ListMut {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_tuple_field1_finish(f, "ListMut",
&&self.0)
}
}Debug)]
56pub struct ListMut(SEXP);
5758impl List {
59/// Return true if the underlying SEXP is a list (VECSXP) according to R.
60 ///
61 /// Uses `SexpExt::is_list` (VECSXP check) — **not** `is_pair_list` (LISTSXP).
62#[inline]
63pub fn is_list(self) -> bool {
64 <SEXPas crate::SexpExt>::is_list(&self.0)
65 }
6667/// Wrap an existing `VECSXP` without additional checks.
68 ///
69 /// # Safety
70 ///
71 /// Caller must ensure `sexp` is a valid list (`VECSXP`) that stays rooted
72 /// while the `List` is used: `List` is a `Copy` view and roots nothing.
73#[inline]
74pub const unsafe fn from_raw(sexp: SEXP) -> Self {
75List(sexp)
76 }
7778/// Get the underlying `SEXP`.
79#[inline]
80pub const fn as_sexp(self) -> SEXP {
81self.0
82}
8384/// Length of the list (number of elements).
85#[inline]
86pub fn len(self) -> isize {
87self.0.xlength()
88 }
8990/// Returns true if the list is empty.
91#[inline]
92pub fn is_empty(self) -> bool {
93self.len() == 0
94}
9596/// Get raw SEXP element at 0-based index. Returns `None` if out of bounds.
97#[inline]
98pub fn get(self, idx: isize) -> Option<SEXP> {
99if idx < 0 || idx >= self.len() {
100return None;
101 }
102Some(self.0.vector_elt(idx))
103 }
104105/// Get element at 0-based index and convert to type `T`.
106 ///
107 /// Returns `None` if index is out of bounds or conversion fails.
108 ///
109 /// The conversion error is discarded, so `T`'s `TryFromSexp::Error` is
110 /// unconstrained — any element type works, not only those whose error is
111 /// `SexpError`. Callers that need the error (e.g. to distinguish "missing"
112 /// from "wrong type") should use [`get`](Self::get) and convert directly.
113#[inline]
114pub fn get_index<T>(self, idx: isize) -> Option<T>
115where
116T: TryFromSexp,
117 {
118let sexp = self.get(idx)?;
119 T::try_from_sexp(sexp).ok()
120 }
121122/// The names this list can be looked up by: `(position, name)` for each
123 /// element whose name is neither `NA` nor invalid UTF-8, in list order.
124 /// Empty names are included. Yields nothing when the list has no `names`
125 /// attribute.
126 ///
127 /// [`get_named_sexp`](Self::get_named_sexp),
128 /// [`first_duplicate_name`](Self::first_duplicate_name), [`NamedList`] and
129 /// the `#[derive(TryFromList)]` field lookup all walk the names through
130 /// this, so they agree on which names count.
131 ///
132 /// The `&str`s point into R's CHARSXP cache; they stay valid while the
133 /// list (which roots its `names` attribute) is reachable.
134fn text_names(self) -> impl Iterator<Item = (isize, &'static str)> {
135let names = self.names();
136let n = if names.is_some() { self.len() } else { 0 };
137 (0..n).filter_map(move |i| {
138let name_sexp = names?.string_elt(i);
139if name_sexp == SEXP::na_string() {
140return None;
141 }
142let name_cstr = unsafe { std::ffi::CStr::from_ptr(name_sexp.r_char()) };
143name_cstr.to_str().ok().map(|s| (i, s))
144 })
145 }
146147/// Get the raw element `SEXP` associated with `name`, without conversion.
148 ///
149 /// Returns the element exactly as stored so callers can convert it with any
150 /// [`TryFromSexp`] error type — not only those whose error is `SexpError`.
151 /// Returns `None` when the list has no `names` attribute or no name matches.
152 ///
153 /// When the name appears more than once, the first element with that name
154 /// is returned, as R's `x[["name"]]` does.
155pub fn get_named_sexp(self, name: &str) -> Option<SEXP> {
156self.text_names()
157 .find(|&(_, s)| s == name)
158 .map(|(i, _)| self.0.vector_elt(i))
159 }
160161/// The first name that appears more than once in the list, if any.
162 ///
163 /// An R list may repeat a name (`list(a = 1, a = 2)`), and a `List`
164 /// argument accepts such a list, as R does. A function that needs each
165 /// name to be unique checks for it itself, and words its own error:
166 ///
167 /// ```no_run
168 /// use miniextendr_api::{List, miniextendr};
169 ///
170 /// #[miniextendr]
171 /// pub fn settings_count(settings: List) -> i32 {
172 /// if let Some(name) = settings.first_duplicate_name() {
173 /// panic!("`settings` has the name {name:?} more than once; each entry needs a unique name");
174 /// }
175 /// settings.len() as i32
176 /// }
177 /// ```
178 ///
179 /// Only names that can identify an element are compared: `NA` and empty
180 /// names are skipped (R lets any number of elements go unnamed), and so
181 /// are names that are not valid UTF-8. Returns `None` for a list without
182 /// a `names` attribute.
183pub fn first_duplicate_name(self) -> Option<String> {
184let mut seen = HashSet::new();
185self.text_names()
186 .map(|(_, s)| s)
187 .filter(|s| !s.is_empty())
188 .find(|s| !seen.insert(*s))
189 .map(str::to_owned)
190 }
191192/// Positions of a derived struct's fields, for the code
193 /// `#[derive(TryFromList)]` generates. Not public API.
194 ///
195 /// `sorted_fields` holds the struct's field names in ascending byte
196 /// order (the derive sorts them); entry `k` of the result is the position
197 /// of the first element named `sorted_fields[k]`, or `None` when no
198 /// element has that name. One pass over the names, `O(n log fields)`.
199 ///
200 /// A field name that appears more than once is an error
201 /// ([`SexpError::DuplicateName`]): the struct could only keep one of the
202 /// values. Other repeated names are ignored, as names that are not fields
203 /// are.
204#[doc(hidden)]
205pub fn __mx_field_positions<const N: usize>(
206self,
207 sorted_fields: [&str; N],
208 ) -> Result<[Option<isize>; N], SexpError> {
209if true {
if !sorted_fields.is_sorted() {
{
::core::panicking::panic_fmt(format_args!("field names must be sorted"));
}
};
};debug_assert!(sorted_fields.is_sorted(), "field names must be sorted");
210let mut positions = [None; N];
211for (i, name) in self.text_names() {
212if let Ok(k) = sorted_fields.binary_search(&name) {
213if positions[k].is_some() {
214return Err(SexpError::DuplicateName(name.to_owned()));
215 }
216 positions[k] = Some(i);
217 }
218 }
219Ok(positions)
220 }
221222/// Get element by name and convert to type `T`.
223 ///
224 /// Returns `None` if name not found or conversion fails.
225 ///
226 /// The conversion error is discarded, so `T`'s `TryFromSexp::Error` is
227 /// unconstrained. Use [`get_named_sexp`](Self::get_named_sexp) and convert
228 /// directly when you need to inspect the conversion failure.
229pub fn get_named<T>(self, name: &str) -> Option<T>
230where
231T: TryFromSexp,
232 {
233let sexp = self.get_named_sexp(name)?;
234 T::try_from_sexp(sexp).ok()
235 }
236237// region: Attribute getters (equivalent to R's GET_* macros)
238239/// Get an arbitrary attribute by symbol, returning `None` for `R_NilValue`.
240#[inline]
241fn get_attr_opt(self, name: SEXP) -> Option<SEXP> {
242let attr = self.0.get_attr(name);
243if attr.is_nil() { None } else { Some(attr) }
244 }
245246/// Get the `names` attribute if present.
247#[inline]
248pub fn names(self) -> Option<SEXP> {
249self.get_attr_opt(SEXP::names_symbol())
250 }
251252/// Get the `class` attribute if present.
253#[inline]
254pub fn get_class(self) -> Option<SEXP> {
255self.get_attr_opt(SEXP::class_symbol())
256 }
257258/// Get the `dim` attribute if present.
259#[inline]
260pub fn get_dim(self) -> Option<SEXP> {
261self.get_attr_opt(SEXP::dim_symbol())
262 }
263264/// Get the `dimnames` attribute if present.
265#[inline]
266pub fn get_dimnames(self) -> Option<SEXP> {
267self.get_attr_opt(SEXP::dimnames_symbol())
268 }
269270/// Get row names from the `dimnames` attribute.
271#[inline]
272pub fn get_rownames(self) -> Option<SEXP> {
273let rownames = unsafe { sys::Rf_GetRowNames(self.0) };
274if rownames.is_nil() {
275None276 } else {
277Some(rownames)
278 }
279 }
280281/// Get column names from the `dimnames` attribute.
282#[inline]
283pub fn get_colnames(self) -> Option<SEXP> {
284let dimnames = self.0.get_dimnames();
285if dimnames.is_nil() {
286return None;
287 }
288let colnames = unsafe { sys::Rf_GetColNames(dimnames) };
289if colnames.is_nil() {
290None291 } else {
292Some(colnames)
293 }
294 }
295296/// Get the `levels` attribute if present (for factors).
297#[inline]
298pub fn get_levels(self) -> Option<SEXP> {
299self.get_attr_opt(SEXP::levels_symbol())
300 }
301302/// Get the `tsp` attribute if present (for time series).
303#[inline]
304pub fn get_tsp(self) -> Option<SEXP> {
305self.get_attr_opt(SEXP::tsp_symbol())
306 }
307// endregion
308309 // region: Attribute setters (equivalent to R's SET_* macros)
310311/// Set the `names` attribute; returns the same list for chaining.
312 ///
313 /// Equivalent to R's `SET_NAMES(x, n)`.
314#[inline]
315pub fn set_names(self, names: SEXP) -> Self {
316self.0.set_names(names);
317self318 }
319320/// Set the `class` attribute; returns the same list for chaining.
321 ///
322 /// Equivalent to R's `SET_CLASS(x, n)`.
323#[inline]
324pub fn set_class(self, class: SEXP) -> Self {
325self.0.set_class(class);
326self327 }
328329/// Set the `dim` attribute; returns the same list for chaining.
330 ///
331 /// Equivalent to R's `SET_DIM(x, n)`.
332#[inline]
333pub fn set_dim(self, dim: SEXP) -> Self {
334self.0.set_dim(dim);
335self336 }
337338/// Set the `dimnames` attribute; returns the same list for chaining.
339 ///
340 /// Equivalent to R's `SET_DIMNAMES(x, n)`.
341#[inline]
342pub fn set_dimnames(self, dimnames: SEXP) -> Self {
343self.0.set_dimnames(dimnames);
344self345 }
346347/// Set the `levels` attribute; returns the same list for chaining.
348 ///
349 /// Equivalent to R's `SET_LEVELS(x, l)`.
350#[inline]
351pub fn set_levels(self, levels: SEXP) -> Self {
352self.0.set_levels(levels);
353self354 }
355// endregion
356357 // region: Convenience setters (string-based)
358359/// Set the `class` attribute from a slice of class names.
360 ///
361 /// This is a convenience wrapper that creates a character vector from the
362 /// provided strings and sets it as the class attribute.
363 ///
364 /// # Example
365 ///
366 /// ```ignore
367 /// let list = List::from_pairs(vec![("x", vec![1, 2, 3])]);
368 /// let df = list.set_class_str(&["data.frame"]);
369 /// ```
370#[inline]
371pub fn set_class_str(self, classes: &[&str]) -> Self {
372use crate::SEXPTYPE::STRSXP;
373374let n: isize = classes375 .len()
376 .try_into()
377 .expect("classes length exceeds isize::MAX");
378unsafe {
379// Protect self across the class-vector allocation; otherwise the
380 // parent list can be freed during `Rf_allocVector` while it sits
381 // unrooted in our Rust handle (UAF under gctorture).
382let _self_guard = OwnedProtect::new(self.0);
383let class_vec = OwnedProtect::new(sys::Rf_allocVector(STRSXP, n));
384for (i, class) in classes.iter().enumerate() {
385let idx: isize = i.try_into().expect("index exceeds isize::MAX");
386 class_vec.get().set_string_elt(idx, SEXP::charsxp(class));
387 }
388self.0.set_class(class_vec.get());
389 }
390self391 }
392393/// Set class = `"data.frame"` using a cached class STRSXP.
394 ///
395 /// Equivalent to `set_class_str(&["data.frame"])` but avoids allocation.
396#[inline]
397pub fn set_data_frame_class(self) -> Self {
398self.0
399.set_class(crate::cached_class::data_frame_class_sexp());
400self401 }
402403/// Set the `names` attribute from a slice of strings.
404 ///
405 /// This is a convenience wrapper that creates a character vector from the
406 /// provided strings and sets it as the names attribute.
407 ///
408 /// # Example
409 ///
410 /// ```ignore
411 /// let list = List::from_values(vec![1, 2, 3]);
412 /// let named = list.set_names_str(&["a", "b", "c"]);
413 /// ```
414#[inline]
415pub fn set_names_str(self, names: &[&str]) -> Self {
416use crate::SEXPTYPE::STRSXP;
417418let n: isize = names419 .len()
420 .try_into()
421 .expect("names length exceeds isize::MAX");
422unsafe {
423// Protect self across the names-vector allocation; see set_class_str.
424let _self_guard = OwnedProtect::new(self.0);
425let names_vec = OwnedProtect::new(sys::Rf_allocVector(STRSXP, n));
426for (i, name) in names.iter().enumerate() {
427let idx: isize = i.try_into().expect("index exceeds isize::MAX");
428 names_vec.get().set_string_elt(idx, SEXP::charsxp(name));
429 }
430self.0.set_names(names_vec.get());
431 }
432self433 }
434435/// Set `row.names` for a data.frame using compact integer form.
436 ///
437 /// R internally represents row.names as a compact integer vector
438 /// `c(NA_integer_, -n)` when the row names are just `1:n`. This is more
439 /// memory-efficient than storing n strings.
440 ///
441 /// # Example
442 ///
443 /// ```ignore
444 /// let list = List::from_pairs(vec![
445 /// ("x", vec![1, 2, 3]),
446 /// ("y", vec![4, 5, 6]),
447 /// ])
448 /// .set_class_str(&["data.frame"])
449 /// .set_row_names_int(3); // Row names: "1", "2", "3"
450 /// ```
451#[inline]
452pub fn set_row_names_int(self, n: usize) -> Self {
453unsafe {
454// Protect self across the row.names allocation; see set_class_str.
455let _self_guard = OwnedProtect::new(self.0);
456// R's compact row.names: c(NA_integer_, -n)
457let (row_names, rn) = crate::into_r::alloc_r_vector::<i32>(2);
458let _guard = OwnedProtect::new(row_names);
459rn[0] = i32::MIN; // NA_INTEGER
460let n_i32 = i32::try_from(n).unwrap_or_else(|_| {
461{
::core::panicking::panic_fmt(format_args!("row count {0} exceeds i32::MAX",
n));
};panic!("row count {n} exceeds i32::MAX");
462 });
463rn[1] = -n_i32;
464self.0.set_row_names(row_names);
465 }
466self467 }
468469/// Set `row.names` from a vector of strings.
470 ///
471 /// Use this when you need custom row names. For simple sequential row names
472 /// (1, 2, 3, ...), use [`set_row_names_int`](Self::set_row_names_int) instead.
473 ///
474 /// # Example
475 ///
476 /// ```ignore
477 /// let list = List::from_pairs(vec![
478 /// ("x", vec![1, 2, 3]),
479 /// ])
480 /// .set_class_str(&["data.frame"])
481 /// .set_row_names_str(&["row_a", "row_b", "row_c"]);
482 /// ```
483#[inline]
484pub fn set_row_names_str(self, row_names: &[&str]) -> Self {
485use crate::SEXPTYPE::STRSXP;
486487let n: isize = row_names488 .len()
489 .try_into()
490 .expect("row_names length exceeds isize::MAX");
491unsafe {
492// Protect self across the row.names allocation; see set_class_str.
493let _self_guard = OwnedProtect::new(self.0);
494let names_vec = OwnedProtect::new(sys::Rf_allocVector(STRSXP, n));
495for (i, name) in row_names.iter().enumerate() {
496let idx: isize = i.try_into().expect("index exceeds isize::MAX");
497 names_vec.get().set_string_elt(idx, SEXP::charsxp(name));
498 }
499self.0.set_row_names(names_vec.get());
500 }
501self502 }
503// endregion
504505 // region: Safe element insertion
506507/// Set an element at the given index, protecting the child during insertion.
508 ///
509 /// This is the safe way to insert a freshly allocated SEXP into a list.
510 /// The child is protected for the duration of the `SET_VECTOR_ELT` call,
511 /// ensuring it cannot be garbage collected.
512 ///
513 /// # Safety
514 ///
515 /// - Must be called from the R main thread
516 /// - `child` must be a valid SEXP
517 /// - `self` must be a valid, protected VECSXP
518 ///
519 /// # Panics
520 ///
521 /// Panics if `idx` is out of bounds.
522 ///
523 /// # Example
524 ///
525 /// ```ignore
526 /// let scope = ProtectScope::new();
527 /// let list = List::from_raw(scope.alloc_vecsxp(n).into_raw());
528 ///
529 /// for i in 0..n {
530 /// let child = Rf_allocVector(REALSXP, 10); // unprotected!
531 /// list.set_elt(i, child); // safe: protects child during insertion
532 /// }
533 /// ```
534#[inline]
535pub unsafe fn set_elt(self, idx: isize, child: SEXP) {
536if !(idx >= 0 && idx < self.len()) {
{ ::core::panicking::panic_fmt(format_args!("index out of bounds")); }
};assert!(idx >= 0 && idx < self.len(), "index out of bounds");
537// Protect child for the duration of SET_VECTOR_ELT.
538 // Once inserted, the child is protected by the parent container.
539 // SAFETY: caller guarantees R main thread and valid SEXPs
540unsafe {
541let _guard = OwnedProtect::new(child);
542self.0.set_vector_elt(idx, child);
543 }
544 }
545546/// Set an element without protecting the child.
547 ///
548 /// # Safety
549 ///
550 /// In addition to the safety requirements of [`set_elt`](Self::set_elt):
551 /// - The caller must ensure `child` is already protected or that no GC
552 /// can occur between child allocation and this call.
553 ///
554 /// Use this for performance when you know the child is already protected
555 /// (e.g., it's a child of another protected container, or you have an
556 /// `OwnedProtect` guard for it).
557#[inline]
558pub unsafe fn set_elt_unchecked(self, idx: isize, child: SEXP) {
559if true {
if !(idx >= 0 && idx < self.len()) {
{ ::core::panicking::panic_fmt(format_args!("index out of bounds")); }
};
};debug_assert!(idx >= 0 && idx < self.len(), "index out of bounds");
560// SAFETY: caller guarantees child is protected and valid
561self.0.set_vector_elt(idx, child);
562 }
563564/// Set an element using a callback that produces the child.
565 ///
566 /// The callback is executed within a protection scope, so any allocations
567 /// it performs are protected until insertion completes.
568 ///
569 /// # Safety
570 ///
571 /// - Must be called from the R main thread
572 /// - `self` must be a valid, protected VECSXP
573 ///
574 /// # Example
575 ///
576 /// ```ignore
577 /// let list = List::from_raw(scope.alloc_vecsxp(n).into_raw());
578 ///
579 /// for i in 0..n {
580 /// list.set_elt_with(i, || {
581 /// let vec = Rf_allocVector(REALSXP, 10);
582 /// fill_vector(vec); // can allocate internally
583 /// vec
584 /// });
585 /// }
586 /// ```
587#[inline]
588pub unsafe fn set_elt_with<F>(self, idx: isize, f: F)
589where
590F: FnOnce() -> SEXP,
591 {
592if !(idx >= 0 && idx < self.len()) {
{ ::core::panicking::panic_fmt(format_args!("index out of bounds")); }
};assert!(idx >= 0 && idx < self.len(), "index out of bounds");
593// SAFETY: caller guarantees R main thread
594unsafe {
595let child = OwnedProtect::new(f());
596self.0.set_vector_elt(idx, child.get());
597 }
598 }
599// endregion
600}
601602// region: ListBuilder - efficient batch list construction
603604use crate::gc_protect::ProtectScope;
605606/// Builder for constructing lists with efficient protection management.
607///
608/// `ListBuilder` holds a reference to a [`ProtectScope`], allowing multiple
609/// elements to be inserted without repeatedly protecting/unprotecting each one.
610/// This is more efficient than using [`List::set_elt`] in a loop.
611///
612/// # Example
613///
614/// ```ignore
615/// unsafe fn build_list(n: isize) -> SEXP {
616/// let scope = ProtectScope::new();
617/// let builder = ListBuilder::new(&scope, n);
618///
619/// for i in 0..n {
620/// // Allocations inside the loop are protected by the scope
621/// let child = scope.alloc_real(10).into_raw();
622/// builder.set(i, child);
623/// }
624///
625/// builder.into_sexp()
626/// }
627/// ```
628pub struct ListBuilder<'a> {
629 list: SEXP,
630 _scope: &'a ProtectScope,
631}
632633impl<'a> ListBuilder<'a> {
634/// Create a new list builder with the given length.
635 ///
636 /// The list is allocated and protected using the provided scope.
637 ///
638 /// # Safety
639 ///
640 /// Must be called from the R main thread.
641#[inline]
642pub unsafe fn new(scope: &'a ProtectScope, len: usize) -> Self {
643// SAFETY: caller guarantees R main thread
644let list = unsafe { scope.alloc_vecsxp(len).into_raw() };
645Self {
646list,
647 _scope: scope,
648 }
649 }
650651/// Create a new list builder via the **unchecked** FFI allocation path.
652 ///
653 /// `_unchecked` twin of [`new`](Self::new): the VECSXP is allocated with
654 /// `Rf_allocVector_unchecked` (see [`ProtectScope::alloc_vecsxp_unchecked`]),
655 /// bypassing the main-thread assertion. Use inside ALTREP callbacks,
656 /// `with_r_unwind_protect`, or `with_r_thread` bodies, and pair element
657 /// insertion with [`set_unchecked`](Self::set_unchecked).
658 ///
659 /// # Safety
660 ///
661 /// Must be called from the R main thread, in a context where the checked-FFI
662 /// assertion is intentionally bypassed (see CLAUDE.md "FFI thread checking").
663#[inline]
664pub unsafe fn new_unchecked(scope: &'a ProtectScope, len: usize) -> Self {
665// SAFETY: caller guarantees R main thread in a checked-bypass context.
666let list = unsafe { scope.alloc_vecsxp_unchecked(len).into_raw() };
667Self {
668list,
669 _scope: scope,
670 }
671 }
672673/// Create a builder wrapping an existing protected list.
674 ///
675 /// # Safety
676 ///
677 /// - Must be called from the R main thread
678 /// - `list` must be a valid, protected VECSXP
679#[inline]
680pub unsafe fn from_protected(scope: &'a ProtectScope, list: SEXP) -> Self {
681Self {
682list,
683 _scope: scope,
684 }
685 }
686687/// Set an element at the given index.
688 ///
689 /// The `child` should be protected by the same scope (or a parent scope).
690 /// Use `scope.protect_raw(...)` before calling this method.
691 ///
692 /// # Safety
693 ///
694 /// - `child` must be a valid SEXP
695 /// - `child` should be protected (typically via the same scope)
696#[inline]
697pub unsafe fn set(&self, idx: isize, child: SEXP) {
698// SAFETY: caller guarantees valid and protected child
699if true {
if !(idx >= 0 && idx < self.list.xlength()) {
::core::panicking::panic("assertion failed: idx >= 0 && idx < self.list.xlength()")
};
};debug_assert!(idx >= 0 && idx < self.list.xlength());
700self.list.set_vector_elt(idx, child);
701 }
702703/// Set an element via the **unchecked** FFI path.
704 ///
705 /// `_unchecked` twin of [`set`](Self::set): inserts with
706 /// `set_vector_elt_unchecked`, bypassing the main-thread assertion. Pair with
707 /// [`new_unchecked`](Self::new_unchecked) inside ALTREP callbacks,
708 /// `with_r_unwind_protect`, or `with_r_thread` bodies.
709 ///
710 /// # Safety
711 ///
712 /// - `child` must be a valid SEXP, already protected (typically by the same
713 /// scope, or by being inserted into this protected parent immediately after
714 /// allocation with no intervening allocation)
715 /// - Must be called from the R main thread, in a checked-bypass context
716#[inline]
717pub unsafe fn set_unchecked(&self, idx: isize, child: SEXP) {
718// SAFETY: caller guarantees valid, protected child in a checked-bypass context.
719if true {
if !(idx >= 0 && idx < self.list.xlength()) {
::core::panicking::panic("assertion failed: idx >= 0 && idx < self.list.xlength()")
};
};debug_assert!(idx >= 0 && idx < self.list.xlength());
720unsafe { self.list.set_vector_elt_unchecked(idx, child) };
721 }
722723/// Set an element, protecting the child within the builder's scope.
724 ///
725 /// This is a convenience method that protects the child and then inserts it.
726 ///
727 /// # Safety
728 ///
729 /// - `child` must be a valid SEXP
730#[inline]
731pub unsafe fn set_protected(&self, idx: isize, child: SEXP) {
732// SAFETY: caller guarantees valid child
733unsafe {
734if true {
if !(idx >= 0 && idx < self.list.xlength()) {
::core::panicking::panic("assertion failed: idx >= 0 && idx < self.list.xlength()")
};
};debug_assert!(idx >= 0 && idx < self.list.xlength());
735let _guard = OwnedProtect::new(child);
736self.list.set_vector_elt(idx, child);
737 }
738 }
739740/// Get the underlying list SEXP.
741#[inline]
742pub fn as_sexp(&self) -> SEXP {
743self.list
744 }
745746/// Convert to a `List` wrapper.
747#[inline]
748pub fn into_list(self) -> List {
749List(self.list)
750 }
751752/// Convert to the underlying SEXP.
753#[inline]
754pub fn into_sexp(self) -> SEXP {
755self.list
756 }
757758/// Get the length of the list.
759#[inline]
760pub fn len(&self) -> isize {
761self.list.xlength()
762 }
763764/// Check if the list is empty.
765#[inline]
766pub fn is_empty(&self) -> bool {
767self.len() == 0
768}
769}
770// endregion
771772mod accumulator;
773mod named;
774775pub use accumulator::*;
776pub use named::*;
777778// region: IntoList and TryFromList traits
779780/// Convert things into an R list.
781pub trait IntoList {
782/// Convert `self` into an R list wrapper.
783fn into_list(self) -> List;
784}
785786/// Fallible conversion from an R list into a Rust value.
787pub trait TryFromList: Sized {
788/// Error returned when conversion fails.
789type Error;
790791/// Attempt to convert an R list wrapper into `Self`.
792fn try_from_list(list: List) -> Result<Self, Self::Error>;
793}
794795// Tuple lists use the same positional representation and batched diagnostics
796// as IntoR/TryFromSexp; no second conversion implementation is needed here.
797macro_rules!impl_tuple_list {
798 ($n:literal, $name:literal; $($T:ident $R:ident $idx:tt),+) => {
799impl<$($T: IntoR),+> IntoList for ($($T,)+) {
800fn into_list(self) -> List {
801 List(self.into_sexp())
802 }
803 }
804805impl<$($T: TryFromSexp),+> TryFromList for ($($T,)+)
806where
807$($T::Error: Into<SexpError>,)+
808 {
809type Error = SexpError;
810811fn try_from_list(list: List) -> Result<Self, Self::Error> {
812Self::try_from_sexp(list.0)
813 }
814 }
815 };
816}
817818impl<A: IntoR> IntoList for (A,) {
fn into_list(self) -> List { List(self.into_sexp()) }
}
impl<A: TryFromSexp> TryFromList for (A,) where A::Error: Into<SexpError> {
type Error = SexpError;
fn try_from_list(list: List) -> Result<Self, Self::Error> {
Self::try_from_sexp(list.0)
}
}
impl<A: IntoR, B: IntoR> IntoList for (A, B) {
fn into_list(self) -> List { List(self.into_sexp()) }
}
impl<A: TryFromSexp, B: TryFromSexp> TryFromList for (A, B) where
A::Error: Into<SexpError>, B::Error: Into<SexpError> {
type Error = SexpError;
fn try_from_list(list: List) -> Result<Self, Self::Error> {
Self::try_from_sexp(list.0)
}
}
impl<A: IntoR, B: IntoR, C: IntoR> IntoList for (A, B, C) {
fn into_list(self) -> List { List(self.into_sexp()) }
}
impl<A: TryFromSexp, B: TryFromSexp, C: TryFromSexp> TryFromList for (A, B, C)
where A::Error: Into<SexpError>, B::Error: Into<SexpError>,
C::Error: Into<SexpError> {
type Error = SexpError;
fn try_from_list(list: List) -> Result<Self, Self::Error> {
Self::try_from_sexp(list.0)
}
}
impl<A: IntoR, B: IntoR, C: IntoR, D: IntoR> IntoList for (A, B, C, D) {
fn into_list(self) -> List { List(self.into_sexp()) }
}
impl<A: TryFromSexp, B: TryFromSexp, C: TryFromSexp, D: TryFromSexp>
TryFromList for (A, B, C, D) where A::Error: Into<SexpError>,
B::Error: Into<SexpError>, C::Error: Into<SexpError>,
D::Error: Into<SexpError> {
type Error = SexpError;
fn try_from_list(list: List) -> Result<Self, Self::Error> {
Self::try_from_sexp(list.0)
}
}
impl<A: IntoR, B: IntoR, C: IntoR, D: IntoR, E: IntoR> IntoList for
(A, B, C, D, E) {
fn into_list(self) -> List { List(self.into_sexp()) }
}
impl<A: TryFromSexp, B: TryFromSexp, C: TryFromSexp, D: TryFromSexp,
E: TryFromSexp> TryFromList for (A, B, C, D, E) where
A::Error: Into<SexpError>, B::Error: Into<SexpError>,
C::Error: Into<SexpError>, D::Error: Into<SexpError>,
E::Error: Into<SexpError> {
type Error = SexpError;
fn try_from_list(list: List) -> Result<Self, Self::Error> {
Self::try_from_sexp(list.0)
}
}
impl<A: IntoR, B: IntoR, C: IntoR, D: IntoR, E: IntoR, F: IntoR> IntoList for
(A, B, C, D, E, F) {
fn into_list(self) -> List { List(self.into_sexp()) }
}
impl<A: TryFromSexp, B: TryFromSexp, C: TryFromSexp, D: TryFromSexp,
E: TryFromSexp, F: TryFromSexp> TryFromList for (A, B, C, D, E, F) where
A::Error: Into<SexpError>, B::Error: Into<SexpError>,
C::Error: Into<SexpError>, D::Error: Into<SexpError>,
E::Error: Into<SexpError>, F::Error: Into<SexpError> {
type Error = SexpError;
fn try_from_list(list: List) -> Result<Self, Self::Error> {
Self::try_from_sexp(list.0)
}
}
impl<A: IntoR, B: IntoR, C: IntoR, D: IntoR, E: IntoR, F: IntoR, G: IntoR>
IntoList for (A, B, C, D, E, F, G) {
fn into_list(self) -> List { List(self.into_sexp()) }
}
impl<A: TryFromSexp, B: TryFromSexp, C: TryFromSexp, D: TryFromSexp,
E: TryFromSexp, F: TryFromSexp, G: TryFromSexp> TryFromList for
(A, B, C, D, E, F, G) where A::Error: Into<SexpError>,
B::Error: Into<SexpError>, C::Error: Into<SexpError>,
D::Error: Into<SexpError>, E::Error: Into<SexpError>,
F::Error: Into<SexpError>, G::Error: Into<SexpError> {
type Error = SexpError;
fn try_from_list(list: List) -> Result<Self, Self::Error> {
Self::try_from_sexp(list.0)
}
}
impl<A: IntoR, B: IntoR, C: IntoR, D: IntoR, E: IntoR, F: IntoR, G: IntoR,
H: IntoR> IntoList for (A, B, C, D, E, F, G, H) {
fn into_list(self) -> List { List(self.into_sexp()) }
}
impl<A: TryFromSexp, B: TryFromSexp, C: TryFromSexp, D: TryFromSexp,
E: TryFromSexp, F: TryFromSexp, G: TryFromSexp, H: TryFromSexp>
TryFromList for (A, B, C, D, E, F, G, H) where A::Error: Into<SexpError>,
B::Error: Into<SexpError>, C::Error: Into<SexpError>,
D::Error: Into<SexpError>, E::Error: Into<SexpError>,
F::Error: Into<SexpError>, G::Error: Into<SexpError>,
H::Error: Into<SexpError> {
type Error = SexpError;
fn try_from_list(list: List) -> Result<Self, Self::Error> {
Self::try_from_sexp(list.0)
}
}
impl<A: IntoR, B: IntoR, C: IntoR, D: IntoR, E: IntoR, F: IntoR, G: IntoR,
H: IntoR, I: IntoR> IntoList for (A, B, C, D, E, F, G, H, I) {
fn into_list(self) -> List { List(self.into_sexp()) }
}
impl<A: TryFromSexp, B: TryFromSexp, C: TryFromSexp, D: TryFromSexp,
E: TryFromSexp, F: TryFromSexp, G: TryFromSexp, H: TryFromSexp,
I: TryFromSexp> TryFromList for (A, B, C, D, E, F, G, H, I) where
A::Error: Into<SexpError>, B::Error: Into<SexpError>,
C::Error: Into<SexpError>, D::Error: Into<SexpError>,
E::Error: Into<SexpError>, F::Error: Into<SexpError>,
G::Error: Into<SexpError>, H::Error: Into<SexpError>,
I::Error: Into<SexpError> {
type Error = SexpError;
fn try_from_list(list: List) -> Result<Self, Self::Error> {
Self::try_from_sexp(list.0)
}
}
impl<A: IntoR, B: IntoR, C: IntoR, D: IntoR, E: IntoR, F: IntoR, G: IntoR,
H: IntoR, I: IntoR, J: IntoR> IntoList for (A, B, C, D, E, F, G, H, I, J)
{
fn into_list(self) -> List { List(self.into_sexp()) }
}
impl<A: TryFromSexp, B: TryFromSexp, C: TryFromSexp, D: TryFromSexp,
E: TryFromSexp, F: TryFromSexp, G: TryFromSexp, H: TryFromSexp,
I: TryFromSexp, J: TryFromSexp> TryFromList for
(A, B, C, D, E, F, G, H, I, J) where A::Error: Into<SexpError>,
B::Error: Into<SexpError>, C::Error: Into<SexpError>,
D::Error: Into<SexpError>, E::Error: Into<SexpError>,
F::Error: Into<SexpError>, G::Error: Into<SexpError>,
H::Error: Into<SexpError>, I::Error: Into<SexpError>,
J::Error: Into<SexpError> {
type Error = SexpError;
fn try_from_list(list: List) -> Result<Self, Self::Error> {
Self::try_from_sexp(list.0)
}
}
impl<A: IntoR, B: IntoR, C: IntoR, D: IntoR, E: IntoR, F: IntoR, G: IntoR,
H: IntoR, I: IntoR, J: IntoR, K: IntoR> IntoList for
(A, B, C, D, E, F, G, H, I, J, K) {
fn into_list(self) -> List { List(self.into_sexp()) }
}
impl<A: TryFromSexp, B: TryFromSexp, C: TryFromSexp, D: TryFromSexp,
E: TryFromSexp, F: TryFromSexp, G: TryFromSexp, H: TryFromSexp,
I: TryFromSexp, J: TryFromSexp, K: TryFromSexp> TryFromList for
(A, B, C, D, E, F, G, H, I, J, K) where A::Error: Into<SexpError>,
B::Error: Into<SexpError>, C::Error: Into<SexpError>,
D::Error: Into<SexpError>, E::Error: Into<SexpError>,
F::Error: Into<SexpError>, G::Error: Into<SexpError>,
H::Error: Into<SexpError>, I::Error: Into<SexpError>,
J::Error: Into<SexpError>, K::Error: Into<SexpError> {
type Error = SexpError;
fn try_from_list(list: List) -> Result<Self, Self::Error> {
Self::try_from_sexp(list.0)
}
}
impl<A: IntoR, B: IntoR, C: IntoR, D: IntoR, E: IntoR, F: IntoR, G: IntoR,
H: IntoR, I: IntoR, J: IntoR, K: IntoR, L: IntoR> IntoList for
(A, B, C, D, E, F, G, H, I, J, K, L) {
fn into_list(self) -> List { List(self.into_sexp()) }
}
impl<A: TryFromSexp, B: TryFromSexp, C: TryFromSexp, D: TryFromSexp,
E: TryFromSexp, F: TryFromSexp, G: TryFromSexp, H: TryFromSexp,
I: TryFromSexp, J: TryFromSexp, K: TryFromSexp, L: TryFromSexp>
TryFromList for (A, B, C, D, E, F, G, H, I, J, K, L) where
A::Error: Into<SexpError>, B::Error: Into<SexpError>,
C::Error: Into<SexpError>, D::Error: Into<SexpError>,
E::Error: Into<SexpError>, F::Error: Into<SexpError>,
G::Error: Into<SexpError>, H::Error: Into<SexpError>,
I::Error: Into<SexpError>, J::Error: Into<SexpError>,
K::Error: Into<SexpError>, L::Error: Into<SexpError> {
type Error = SexpError;
fn try_from_list(list: List) -> Result<Self, Self::Error> {
Self::try_from_sexp(list.0)
}
}crate::tuple_macros::for_each_tuple!(impl_tuple_list);
819820impl<T: IntoR> IntoListfor Vec<T> {
821fn into_list(self) -> List {
822// Allocate + protect the parent first, then call `into_sexp()` per
823 // element and write straight into the parent. Pre-collecting elements
824 // into `Vec<SEXP>` would leave them unrooted across allocations — same
825 // UAF shape as the columnar `Generic` buffer (PR #424 / issue #307).
826let n: isize = self827 .len()
828 .try_into()
829 .expect("list length exceeds isize::MAX");
830unsafe {
831let list = OwnedProtect::new(sys::Rf_allocVector(VECSXP, n));
832for (i, val) in self.into_iter().enumerate() {
833let idx: isize = i.try_into().expect("index exceeds isize::MAX");
834 list.get().set_vector_elt(idx, val.into_sexp());
835 }
836List(list.get())
837 }
838 }
839}
840841impl<T> TryFromListfor Vec<T>
842where
843T: TryFromSexp<Error = SexpError>,
844{
845type Error = SexpError;
846847fn try_from_list(list: List) -> Result<Self, Self::Error> {
848let expected: usize = list849 .len()
850 .try_into()
851 .expect("list length must be non-negative");
852let mut out = Vec::with_capacity(expected);
853for i in 0..expected {
854let idx: isize = i.try_into().expect("index exceeds isize::MAX");
855let sexp = list.get(idx).ok_or_else(|| {
856 SexpError::from(SexpLengthError {
857 expected,
858 actual: i,
859 })
860 })?;
861 out.push(TryFromSexp::try_from_sexp(sexp)?);
862 }
863Ok(out)
864 }
865}
866867// endregion
868869// region: HashMap conversions
870871impl<K, V> IntoListfor HashMap<K, V>
872where
873K: AsRef<str>,
874 V: IntoR,
875{
876fn into_list(self) -> List {
877let pairs: Vec<(K, V)> = self.into_iter().collect();
878List::from_pairs(pairs)
879 }
880}
881882/// Keys are the element names; an element without a usable name (no `names`
883/// attribute, `NA`, or not valid UTF-8) is keyed by its 0-based position, and
884/// empty names share the key `""` (the last one wins). A non-empty name that
885/// appears more than once is an error ([`SexpError::DuplicateName`]), as for
886/// the `TryFromSexp` map conversion.
887impl<V> TryFromListfor HashMap<String, V>
888where
889V: TryFromSexp<Error = SexpError>,
890{
891type Error = SexpError;
892893fn try_from_list(list: List) -> Result<Self, Self::Error> {
894// A repeated name would leave only one of its values in the map.
895if let Some(name) = list.first_duplicate_name() {
896return Err(SexpError::DuplicateName(name));
897 }
898let n: usize = list899 .len()
900 .try_into()
901 .expect("list length must be non-negative");
902let names_sexp = list.names();
903let mut map = HashMap::with_capacity(n);
904905for i in 0..n {
906let idx: isize = i.try_into().expect("index exceeds isize::MAX");
907let sexp = list.get(idx).ok_or_else(|| {
908 SexpError::from(SexpLengthError {
909 expected: n,
910 actual: i,
911 })
912 })?;
913let value: V = TryFromSexp::try_from_sexp(sexp)?;
914915let key = if let Some(names) = names_sexp {
916let name_sexp = names.string_elt(idx);
917if name_sexp == SEXP::na_string() {
918::alloc::__export::must_use({ ::alloc::fmt::format(format_args!("{0}", i)) })format!("{i}")919 } else {
920let name_ptr = name_sexp.r_char();
921let name_cstr = unsafe { std::ffi::CStr::from_ptr(name_ptr) };
922 name_cstr.to_str().unwrap_or(&::alloc::__export::must_use({ ::alloc::fmt::format(format_args!("{0}", i)) })format!("{i}")).to_string()
923 }
924 } else {
925::alloc::__export::must_use({ ::alloc::fmt::format(format_args!("{0}", i)) })format!("{i}")926 };
927928 map.insert(key, value);
929 }
930Ok(map)
931 }
932}
933// endregion
934935// region: BTreeMap conversions
936937impl<K, V> IntoListfor BTreeMap<K, V>
938where
939K: AsRef<str>,
940 V: IntoR,
941{
942fn into_list(self) -> List {
943let pairs: Vec<(K, V)> = self.into_iter().collect();
944List::from_pairs(pairs)
945 }
946}
947948/// Keys are the element names; an element without a usable name (no `names`
949/// attribute, `NA`, or not valid UTF-8) is keyed by its 0-based position, and
950/// empty names share the key `""` (the last one wins). A non-empty name that
951/// appears more than once is an error ([`SexpError::DuplicateName`]), as for
952/// the `TryFromSexp` map conversion.
953impl<V> TryFromListfor BTreeMap<String, V>
954where
955V: TryFromSexp<Error = SexpError>,
956{
957type Error = SexpError;
958959fn try_from_list(list: List) -> Result<Self, Self::Error> {
960// A repeated name would leave only one of its values in the map.
961if let Some(name) = list.first_duplicate_name() {
962return Err(SexpError::DuplicateName(name));
963 }
964let n: usize = list965 .len()
966 .try_into()
967 .expect("list length must be non-negative");
968let names_sexp = list.names();
969let mut map = BTreeMap::new();
970971for i in 0..n {
972let idx: isize = i.try_into().expect("index exceeds isize::MAX");
973let sexp = list.get(idx).ok_or_else(|| {
974 SexpError::from(SexpLengthError {
975 expected: n,
976 actual: i,
977 })
978 })?;
979let value: V = TryFromSexp::try_from_sexp(sexp)?;
980981let key = if let Some(names) = names_sexp {
982let name_sexp = names.string_elt(idx);
983if name_sexp == SEXP::na_string() {
984::alloc::__export::must_use({ ::alloc::fmt::format(format_args!("{0}", i)) })format!("{i}")985 } else {
986let name_ptr = name_sexp.r_char();
987let name_cstr = unsafe { std::ffi::CStr::from_ptr(name_ptr) };
988 name_cstr.to_str().unwrap_or(&::alloc::__export::must_use({ ::alloc::fmt::format(format_args!("{0}", i)) })format!("{i}")).to_string()
989 }
990 } else {
991::alloc::__export::must_use({ ::alloc::fmt::format(format_args!("{0}", i)) })format!("{i}")992 };
993994 map.insert(key, value);
995 }
996Ok(map)
997 }
998}
999// endregion
10001001// region: HashSet conversions (unnamed list <-> set)
10021003impl<T> IntoListfor HashSet<T>
1004where
1005T: IntoR,
1006{
1007fn into_list(self) -> List {
1008let values: Vec<T> = self.into_iter().collect();
1009values.into_list()
1010 }
1011}
10121013impl<T> TryFromListfor HashSet<T>
1014where
1015T: TryFromSexp<Error = SexpError> + Eq + Hash,
1016{
1017type Error = SexpError;
10181019fn try_from_list(list: List) -> Result<Self, Self::Error> {
1020let vec: Vec<T> = TryFromList::try_from_list(list)?;
1021Ok(vec.into_iter().collect())
1022 }
1023}
1024// endregion
10251026// region: BTreeSet conversions (unnamed list <-> set)
10271028impl<T> IntoListfor BTreeSet<T>
1029where
1030T: IntoR,
1031{
1032fn into_list(self) -> List {
1033let values: Vec<T> = self.into_iter().collect();
1034values.into_list()
1035 }
1036}
10371038impl<T> TryFromListfor BTreeSet<T>
1039where
1040T: TryFromSexp<Error = SexpError> + Ord,
1041{
1042type Error = SexpError;
10431044fn try_from_list(list: List) -> Result<Self, Self::Error> {
1045let vec: Vec<T> = TryFromList::try_from_list(list)?;
1046Ok(vec.into_iter().collect())
1047 }
1048}
10491050impl List {
1051/// Build a list from `(name, value)` pairs, setting `names` in one pass.
1052pub fn from_pairs<N, T>(pairs: Vec<(N, T)>) -> Self
1053where
1054N: AsRef<str>,
1055 T: IntoR,
1056 {
1057// Allocate + protect the parent list and names before calling
1058 // `into_sexp()` on each value. Pre-collecting `Vec<(N, SEXP)>` would
1059 // leave the value SEXPs unrooted across subsequent `into_sexp()` and
1060 // the names allocation — same UAF shape as #307.
1061let n: isize = pairs1062 .len()
1063 .try_into()
1064 .expect("pairs length exceeds isize::MAX");
1065unsafe {
1066let list = OwnedProtect::new(sys::Rf_allocVector(VECSXP, n));
1067let names = OwnedProtect::new(sys::Rf_allocVector(STRSXP, n));
1068for (i, (name, val)) in pairs.into_iter().enumerate() {
1069let idx: isize = i.try_into().expect("index exceeds isize::MAX");
1070 list.get().set_vector_elt(idx, val.into_sexp());
1071 names
1072 .get()
1073 .set_string_elt(idx, SEXP::charsxp(name.as_ref()));
1074 }
1075list.get().set_names(names.get());
1076List(list.get())
1077 }
1078 }
10791080/// Build an unnamed list from values.
1081 ///
1082 /// Use this for tuple-like structures where positional access is more natural.
1083 ///
1084 /// # Example
1085 ///
1086 /// ```ignore
1087 /// let list = List::from_values(vec![1i32, 2i32, 3i32]);
1088 /// // R: list(1L, 2L, 3L) - accessed as [[1]], [[2]], [[3]]
1089 /// ```
1090pub fn from_values<T: IntoR>(values: Vec<T>) -> Self {
1091values.into_list()
1092 }
10931094/// Build an unnamed list from pre-converted SEXPs.
1095 ///
1096 /// # Safety Note
1097 ///
1098 /// The input SEXPs should already be protected or be children of protected
1099 /// containers. This function protects the list during construction.
1100pub fn from_raw_values(values: Vec<SEXP>) -> Self {
1101let n: isize = values1102 .len()
1103 .try_into()
1104 .expect("values length exceeds isize::MAX");
1105unsafe {
1106// Protect list during construction. SET_VECTOR_ELT doesn't allocate,
1107 // but we protect defensively in case this code is modified later.
1108let list = OwnedProtect::new(sys::Rf_allocVector(VECSXP, n));
1109for (i, val) in values.into_iter().enumerate() {
1110let idx: isize = i.try_into().expect("index exceeds isize::MAX");
1111 list.get().set_vector_elt(idx, val);
1112 }
1113List(list.get())
1114 }
1115 }
11161117/// Build an atomic vector from homogeneous length-1 scalar SEXPs.
1118 ///
1119 /// If all elements are length-1 scalars of the same coalesceable type
1120 /// (INTSXP, REALSXP, LGLSXP, STRSXP), returns that atomic vector.
1121 /// Otherwise returns a VECSXP (generic list).
1122 ///
1123 /// This is the canonical entry point for both `DataFrame::into_data_frame`
1124 /// (column building) and `SeqSerializer::end` (sequence coalescing).
1125 ///
1126 /// # Safety Note
1127 ///
1128 /// The input SEXPs should already be protected or be children of protected
1129 /// containers.
1130pub fn from_scalars_or_list(elements: &[SEXP]) -> Self {
1131use crate::SEXPTYPE;
1132use crate::into_r::alloc_r_vector;
11331134if elements.is_empty() {
1135return Self::from_raw_values(Vec::new());
1136 }
11371138let first_type = elements[0].type_of();
1139let all_scalar_same_type = elements1140 .iter()
1141 .all(|&e| e.xlength() == 1 && e.type_of() == first_type);
11421143if !all_scalar_same_type {
1144return Self::from_raw_values(elements.to_vec());
1145 }
11461147let n = elements.len();
1148let sexp = match first_type {
1149// For native types: allocate R vector, get mutable slice, read source
1150 // scalars via as_slice()[0] — no per-element FFI calls.
1151SEXPTYPE::INTSXP => unsafe {
1152let (v, dst) = alloc_r_vector::<i32>(n);
1153for (slot, &elem) in dst.iter_mut().zip(elements.iter()) {
1154*slot = *elem.as_slice::<i32>().first().expect("scalar has length 1");
1155 }
1156v1157 },
1158 SEXPTYPE::REALSXP => unsafe {
1159let (v, dst) = alloc_r_vector::<f64>(n);
1160for (slot, &elem) in dst.iter_mut().zip(elements.iter()) {
1161*slot = *elem.as_slice::<f64>().first().expect("scalar has length 1");
1162 }
1163v1164 },
1165 SEXPTYPE::LGLSXP => unsafe {
1166let (v, dst) = alloc_r_vector::<crate::RLogical>(n);
1167for (slot, &elem) in dst.iter_mut().zip(elements.iter()) {
1168*slot = *elem
1169 .as_slice::<crate::RLogical>()
1170 .first()
1171 .expect("scalar has length 1");
1172 }
1173v1174 },
1175// STRSXP elements are CHARSXPs — must use SET_STRING_ELT (no slice access).
1176SEXPTYPE::STRSXP => unsafe {
1177let v = OwnedProtect::new(sys::Rf_allocVector(SEXPTYPE::STRSXP, nas isize));
1178for (i, &elem) in elements.iter().enumerate() {
1179let idx: isize = i.try_into().expect("index exceeds isize::MAX");
1180 v.get().set_string_elt(idx, elem.string_elt(0));
1181 }
1182v.get()
1183 },
1184_ => return Self::from_raw_values(elements.to_vec()),
1185 };
1186List(sexp)
1187 }
11881189/// Build a list from `(name, SEXP)` pairs (heterogeneous-friendly).
1190 ///
1191 /// # Safety Note
1192 ///
1193 /// The input SEXPs should already be protected or be children of protected
1194 /// containers. This function protects the list and names vector during
1195 /// construction.
1196pub fn from_raw_pairs<N>(pairs: Vec<(N, SEXP)>) -> Self
1197where
1198N: AsRef<str>,
1199 {
1200let n: isize = pairs1201 .len()
1202 .try_into()
1203 .expect("pairs length exceeds isize::MAX");
1204unsafe {
1205// CRITICAL: Both list and names must be protected because
1206 // Rf_mkCharLenCE can allocate and trigger GC in the loop below.
1207let list = OwnedProtect::new(sys::Rf_allocVector(VECSXP, n));
1208let names = OwnedProtect::new(sys::Rf_allocVector(STRSXP, n));
1209for (i, (name, val)) in pairs.into_iter().enumerate() {
1210let idx: isize = i.try_into().expect("index exceeds isize::MAX");
1211 list.get().set_vector_elt(idx, val);
12121213let s = name.as_ref();
1214// SEXP::charsxp allocates - list and names must be protected!
1215names.get().set_string_elt(idx, SEXP::charsxp(s));
1216 }
1217list.get().set_names(names.get());
1218List(list.get())
1219 }
1220 }
12211222/// Build an empty named-list SEXP (zero elements, `names` attribute set).
1223 ///
1224 /// Equivalent to [`Self::from_raw_pairs`]`(vec![])`, but avoids the
1225 /// `Vec<(&str, SEXP)>` type annotation that Rust requires at empty-vector
1226 /// callsites where type inference cannot resolve the element type.
1227 ///
1228 /// Codegen paths that emit an empty `from_raw_pairs` call (e.g. unit-variant
1229 /// partitions in `#[derive(DataFrameRow)]`) use this helper so that a future
1230 /// signature change to `from_raw_pairs` only needs to be updated in one
1231 /// place.
1232#[must_use]
1233pub fn from_raw_pairs_empty() -> Self {
1234Self::from_raw_pairs(Vec::<(&str, SEXP)>::new())
1235 }
1236}
12371238impl IntoRfor List {
1239type Error = std::convert::Infallible;
1240fn try_into_sexp(self) -> Result<SEXP, Self::Error> {
1241Ok(self.into_sexp())
1242 }
1243unsafe fn try_into_sexp_unchecked(self) -> Result<SEXP, Self::Error> {
1244self.try_into_sexp()
1245 }
1246#[inline]
1247fn into_sexp(self) -> SEXP {
1248self.0
1249}
1250}
12511252impl IntoRfor ListMut {
1253type Error = std::convert::Infallible;
1254fn try_into_sexp(self) -> Result<SEXP, Self::Error> {
1255Ok(self.into_sexp())
1256 }
1257unsafe fn try_into_sexp_unchecked(self) -> Result<SEXP, Self::Error> {
1258self.try_into_sexp()
1259 }
1260#[inline]
1261fn into_sexp(self) -> SEXP {
1262self.0
1263}
1264}
12651266/// `Some(list)` → the list, `None` → `NULL`: the inverse of
1267/// `TryFromSexp for Option<List>`.
1268impl IntoRfor Option<List> {
1269type Error = std::convert::Infallible;
1270fn try_into_sexp(self) -> Result<SEXP, Self::Error> {
1271Ok(self.into_sexp())
1272 }
1273unsafe fn try_into_sexp_unchecked(self) -> Result<SEXP, Self::Error> {
1274self.try_into_sexp()
1275 }
1276#[inline]
1277fn into_sexp(self) -> SEXP {
1278self.map_or_else(SEXP::nil, |list| list.0)
1279 }
1280}
12811282/// `Some(list)` → the list, `None` → `NULL`: the inverse of
1283/// `TryFromSexp for Option<ListMut>`.
1284impl IntoRfor Option<ListMut> {
1285type Error = std::convert::Infallible;
1286fn try_into_sexp(self) -> Result<SEXP, Self::Error> {
1287Ok(self.into_sexp())
1288 }
1289unsafe fn try_into_sexp_unchecked(self) -> Result<SEXP, Self::Error> {
1290self.try_into_sexp()
1291 }
1292#[inline]
1293fn into_sexp(self) -> SEXP {
1294self.map_or_else(SEXP::nil, |list| list.0)
1295 }
1296}
12971298/// Build an R list-column (VECSXP) from values converted one at a time through
1299/// [`IntoList`].
1300///
1301/// The column is allocated and protected first; each value's list is stored in
1302/// it as soon as it is built, so no list is ever unrooted while the next one
1303/// allocates. Collecting a `Vec<List>` first and converting that would leave
1304/// every earlier list unrooted across the later allocations (#1748).
1305///
1306/// Used by `#[derive(DataFrameRow)]` for `#[dataframe(as_list)]` struct-typed
1307/// fields.
1308#[doc(hidden)]
1309pub fn into_list_column<T: IntoList>(values: Vec<T>) -> SEXP {
1310let n: isize = values1311 .len()
1312 .try_into()
1313 .expect("list-column length exceeds isize::MAX");
1314unsafe {
1315let out = OwnedProtect::new(sys::Rf_allocVector(VECSXP, n));
1316for (i, value) in values.into_iter().enumerate() {
1317let idx: isize = i.try_into().expect("index exceeds isize::MAX");
1318// No allocation between building the list and storing it.
1319out.get().set_vector_elt(idx, value.into_list().0);
1320 }
1321out.get()
1322 }
1323}
13241325/// [`into_list_column`] for optional cells: `None` becomes `NULL`.
1326#[doc(hidden)]
1327pub fn into_option_list_column<T: IntoList>(values: Vec<Option<T>>) -> SEXP {
1328let n: isize = values1329 .len()
1330 .try_into()
1331 .expect("list-column length exceeds isize::MAX");
1332unsafe {
1333// VECSXP slots start as R_NilValue, so `None` cells need no write.
1334let out = OwnedProtect::new(sys::Rf_allocVector(VECSXP, n));
1335for (i, value) in values.into_iter().enumerate() {
1336if let Some(value) = value {
1337let idx: isize = i.try_into().expect("index exceeds isize::MAX");
1338 out.get().set_vector_elt(idx, value.into_list().0);
1339 }
1340 }
1341out.get()
1342 }
1343}
13441345/// Convert a `Vec<List>` to an R list-column (VECSXP).
1346///
1347/// Each element becomes an R list in the output VECSXP. The elements must be
1348/// rooted by the caller (for example, elements of an argument list): this
1349/// allocates the column first. To build the lists as you go, use
1350/// [`into_list_column`].
1351impl IntoRfor Vec<List> {
1352type Error = std::convert::Infallible;
1353fn try_into_sexp(self) -> Result<SEXP, Self::Error> {
1354Ok(self.into_sexp())
1355 }
1356unsafe fn try_into_sexp_unchecked(self) -> Result<SEXP, Self::Error> {
1357self.try_into_sexp()
1358 }
1359fn into_sexp(self) -> SEXP {
1360unsafe {
1361use crate::gc_protect::OwnedProtect;
1362use crate::sys::Rf_allocVector;
1363use crate::{SEXPTYPE, SexpExtas _};
1364let n = self.len() as crate::R_xlen_t;
1365// OwnedProtect guards `out` across the per-element fill; its Drop
1366 // issues the matching UNPROTECT(1) (RAII — count balanced by
1367 // construction).
1368let out = OwnedProtect::new(Rf_allocVector(SEXPTYPE::VECSXP, n));
1369for (i, list) in self.into_iter().enumerate() {
1370 out.get().set_vector_elt(i as crate::R_xlen_t, list.0);
1371 }
1372*out1373 }
1374 }
1375}
13761377/// Convert a `Vec<Option<List>>` to an R list-column (VECSXP).
1378///
1379/// `Some(list)` elements are placed directly as list elements; `None` elements
1380/// become `R_NilValue`. As with `Vec<List>`, the elements must be rooted by
1381/// the caller; [`into_option_list_column`] builds them as it goes.
1382impl IntoRfor Vec<Option<List>> {
1383type Error = std::convert::Infallible;
1384fn try_into_sexp(self) -> Result<SEXP, Self::Error> {
1385Ok(self.into_sexp())
1386 }
1387unsafe fn try_into_sexp_unchecked(self) -> Result<SEXP, Self::Error> {
1388self.try_into_sexp()
1389 }
1390fn into_sexp(self) -> SEXP {
1391unsafe {
1392use crate::gc_protect::OwnedProtect;
1393use crate::sys::Rf_allocVector;
1394use crate::{SEXPTYPE, SexpExtas _};
1395let n = self.len() as crate::R_xlen_t;
1396// VECSXP slots are zero-initialised to R_NilValue by Rf_allocVector,
1397 // so None elements require no explicit fill. OwnedProtect guards
1398 // `out` across the fill and issues the matching UNPROTECT(1) on Drop.
1399let out = OwnedProtect::new(Rf_allocVector(SEXPTYPE::VECSXP, n));
1400for (i, opt) in self.into_iter().enumerate() {
1401if let Some(list) = opt {
1402 out.get().set_vector_elt(i as crate::R_xlen_t, list.0);
1403 }
1404 }
1405*out1406 }
1407 }
1408}
14091410/// A `List` reads any R list, whatever its names.
1411///
1412/// Accepts a `VECSXP` only; anything else is a [`SexpTypeError`]. A pairlist
1413/// (`LISTSXP`, such as `formals(f)` or `pairlist(...)`) is refused, not
1414/// coerced (#1866): the coerced copy would be a new object that nothing
1415/// roots, since `List` is a `Copy` view, so the first allocation in the
1416/// function could free it. The caller converts with `as.list()`, and the
1417/// argument error says so.
1418///
1419/// Names are not checked: R allows a name to appear more than once
1420/// (`list(a = 1, a = 2)`), and so does `List`. A function that needs unique
1421/// names checks with [`List::first_duplicate_name`].
1422impl TryFromSexpfor List {
1423type Error = SexpTypeError;
14241425fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1426let actual = sexp.type_of();
1427if actual == VECSXP {
1428Ok(List(sexp))
1429 } else {
1430Err(SexpTypeError {
1431 expected: VECSXP,
1432actual,
1433 })
1434 }
1435 }
1436}
14371438impl TryFromSexpfor Option<List> {
1439type Error = SexpError;
14401441fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1442if sexp == SEXP::nil() {
1443return Ok(None);
1444 }
1445Ok(Some(List::try_from_sexp(sexp)?))
1446 }
1447}
14481449/// One element of a list of lists: a `VECSXP`. A pairlist element is refused,
1450/// as `List` refuses a pairlist: a coerced copy would be a new object that
1451/// nothing roots while the rest of the list is read.
1452fn list_element(elem: SEXP) -> Result<List, SexpError> {
1453let actual = elem.type_of();
1454if actual == VECSXP {
1455Ok(List(elem))
1456 } else {
1457Err(SexpTypeError {
1458 expected: VECSXP,
1459actual,
1460 }
1461 .into())
1462 }
1463}
14641465/// A list of lists (a list of R objects, such as model objects), read element
1466/// by element (#1837).
1467///
1468/// The argument must be a list (`VECSXP`), and each element a list too. A
1469/// pairlist is refused at either level, as for a `List` argument (#1866).
1470/// Every element that fails is reported in one
1471/// error, with its position as R counts it: `expected list, got numeric
1472/// (elements 2, 3)`. The elements stay rooted by the argument list.
1473///
1474/// A `Vec` of a `#[derive(TryFromSexp)]` newtype over `List` reads through
1475/// this, and runs the newtype's check (`#[try_from_sexp(validate = ...)]`) on
1476/// each element, where each object keeps its class, not on the outer list.
1477impl TryFromSexpfor Vec<List> {
1478type Error = SexpError;
14791480#[inline]
1481fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1482Self::__mx_try_from_sexp_with_check(sexp, &no_element_check)
1483 }
14841485#[inline]
1486unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1487Self::try_from_sexp(sexp)
1488 }
14891490/// `check` runs on each element, before the element is read.
1491fn __mx_try_from_sexp_with_check(
1492 sexp: SEXP,
1493 check: &ElementCheck<'_>,
1494 ) -> Result<Self, SexpError> {
1495map_vecsxp_batched(sexp, |elem| {
1496 check(elem)?;
1497list_element(elem)
1498 })
1499 }
15001501#[inline]
1502unsafe fn __mx_try_from_sexp_unchecked_with_check(
1503 sexp: SEXP,
1504 check: &ElementCheck<'_>,
1505 ) -> Result<Self, SexpError> {
1506Self::__mx_try_from_sexp_with_check(sexp, check)
1507 }
1508}
15091510/// A list of lists and `NULL`s, read element by element: a `NULL` element is
1511/// `None`, any other element is read as for `Vec<List>`, with every failing
1512/// element reported in one error.
1513///
1514/// For a `Vec<Option<T>>` of a newtype over `List`, the newtype's check runs
1515/// on each element but `NULL`, which stays "not given", as for `Option<T>`.
1516impl TryFromSexpfor Vec<Option<List>> {
1517type Error = SexpError;
15181519#[inline]
1520fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1521Self::__mx_try_from_sexp_with_check(sexp, &no_element_check)
1522 }
15231524#[inline]
1525unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
1526Self::try_from_sexp(sexp)
1527 }
15281529/// `check` runs on each element but `NULL`, before the element is read.
1530fn __mx_try_from_sexp_with_check(
1531 sexp: SEXP,
1532 check: &ElementCheck<'_>,
1533 ) -> Result<Self, SexpError> {
1534map_vecsxp_batched(sexp, |elem| {
1535if elem.type_of() == NILSXP {
1536return Ok(None);
1537 }
1538 check(elem)?;
1539list_element(elem).map(Some)
1540 })
1541 }
15421543#[inline]
1544unsafe fn __mx_try_from_sexp_unchecked_with_check(
1545 sexp: SEXP,
1546 check: &ElementCheck<'_>,
1547 ) -> Result<Self, SexpError> {
1548Self::__mx_try_from_sexp_with_check(sexp, check)
1549 }
1550}
15511552impl TryFromSexpfor Option<ListMut> {
1553type Error = SexpError;
15541555fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1556if sexp == SEXP::nil() {
1557return Ok(None);
1558 }
1559let list = ListMut::try_from_sexp(sexp)?;
1560Ok(Some(list))
1561 }
1562}
15631564impl TryFromSexpfor ListMut {
1565type Error = SexpError;
15661567fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
1568let actual = sexp.type_of();
1569if actual != VECSXP {
1570return Err(SexpTypeError {
1571 expected: VECSXP,
1572actual,
1573 }
1574 .into());
1575 }
1576Ok(ListMut(sexp))
1577 }
1578}
1579// endregion