Skip to main content

miniextendr_api/into_r/
collections.rs

1//! Collection conversions (HashMap, BTreeMap, HashSet, BTreeSet) to R.
2//!
3//! - `HashMap<String, V>` / `BTreeMap<String, V>` → named R list
4//! - `HashSet<T>` / `BTreeSet<T>` → unnamed R vector (via Vec intermediary)
5//!
6//! # Tradeoff
7//!
8//! Choose `BTreeMap` over `HashMap` when stable element order in the resulting
9//! R list matters (testthat snapshots, deterministic file output) — `HashMap`
10//! iteration order is unspecified and varies between runs. The same applies
11//! to `BTreeSet` vs `HashSet`. Failure mode of using `HashMap` for a result
12//! the user `expect_equal()`s by position: flaky tests across runs / R
13//! versions.
14//!
15//! Inbound counterpart: `crate::from_r::collections`.
16
17use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
18use std::hash::Hash;
19
20use crate::SexpExt;
21use crate::into_r::{IntoR, str_to_charsxp, str_to_charsxp_unchecked};
22
23macro_rules! impl_map_into_r {
24    ($(#[$meta:meta])* $map_ty:ident) => {
25        $(#[$meta])*
26        impl<V: IntoR> IntoR for $map_ty<String, V> {
27            type Error = crate::into_r_error::IntoRError;
28            fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
29                Ok(self.into_sexp())
30            }
31            unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
32                Ok(unsafe { self.into_sexp_unchecked() })
33            }
34            fn into_sexp(self) -> crate::SEXP {
35                map_to_named_list(self.into_iter())
36            }
37            unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
38                unsafe { map_to_named_list_unchecked(self.into_iter()) }
39            }
40        }
41    };
42}
43
44impl_map_into_r!(
45    /// Convert HashMap<String, V> to R named list (VECSXP).
46    HashMap
47);
48impl_map_into_r!(
49    /// Convert BTreeMap<String, V> to R named list (VECSXP).
50    BTreeMap
51);
52
53/// Helper to convert an iterator of (String, V) pairs to a named R list.
54fn map_to_named_list<V: IntoR>(iter: impl ExactSizeIterator<Item = (String, V)>) -> crate::SEXP {
55    unsafe {
56        let n: crate::R_xlen_t = iter
57            .len()
58            .try_into()
59            .expect("map length exceeds isize::MAX");
60        let list = crate::sys::Rf_allocVector(crate::SEXPTYPE::VECSXP, n);
61        crate::sys::Rf_protect(list);
62
63        // Allocate names vector
64        let names = crate::sys::Rf_allocVector(crate::SEXPTYPE::STRSXP, n);
65        crate::sys::Rf_protect(names);
66
67        for (i, (key, value)) in iter.enumerate() {
68            let idx: crate::R_xlen_t = i.try_into().expect("index exceeds isize::MAX");
69            // Set list element
70            list.set_vector_elt(idx, value.into_sexp());
71
72            // Set name
73            let charsxp = str_to_charsxp(&key);
74            names.set_string_elt(idx, charsxp);
75        }
76
77        // Attach names attribute
78        list.set_names(names);
79
80        crate::sys::Rf_unprotect(2);
81        list
82    }
83}
84
85/// Unchecked version of [`map_to_named_list`].
86unsafe fn map_to_named_list_unchecked<V: IntoR>(
87    iter: impl ExactSizeIterator<Item = (String, V)>,
88) -> crate::SEXP {
89    unsafe {
90        let n: crate::R_xlen_t = iter
91            .len()
92            .try_into()
93            .expect("map length exceeds isize::MAX");
94        let list = crate::sys::Rf_allocVector_unchecked(crate::SEXPTYPE::VECSXP, n);
95        crate::sys::Rf_protect(list);
96
97        let names = crate::sys::Rf_allocVector_unchecked(crate::SEXPTYPE::STRSXP, n);
98        crate::sys::Rf_protect(names);
99
100        for (i, (key, value)) in iter.enumerate() {
101            let idx: crate::R_xlen_t = i.try_into().expect("index exceeds isize::MAX");
102            list.set_vector_elt_unchecked(idx, value.into_sexp_unchecked());
103
104            let charsxp = str_to_charsxp_unchecked(&key);
105            names.set_string_elt_unchecked(idx, charsxp);
106        }
107
108        list.set_attr_unchecked(crate::SEXP::names_symbol(), names);
109
110        crate::sys::Rf_unprotect(2);
111        list
112    }
113}
114
115/// Convert `HashSet<T>` to R vector.
116impl<T> IntoR for HashSet<T>
117where
118    T: crate::RNativeType + Eq + Hash,
119{
120    type Error = std::convert::Infallible;
121    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
122        Ok(self.into_sexp())
123    }
124    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
125        Ok(unsafe { self.into_sexp_unchecked() })
126    }
127    fn into_sexp(self) -> crate::SEXP {
128        let vec: Vec<T> = self.into_iter().collect();
129        vec.into_sexp()
130    }
131    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
132        let vec: Vec<T> = self.into_iter().collect();
133        unsafe { vec.into_sexp_unchecked() }
134    }
135}
136
137/// Convert `BTreeSet<T>` to R vector.
138impl<T> IntoR for BTreeSet<T>
139where
140    T: crate::RNativeType + Ord,
141{
142    type Error = std::convert::Infallible;
143    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
144        Ok(self.into_sexp())
145    }
146    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
147        Ok(unsafe { self.into_sexp_unchecked() })
148    }
149    fn into_sexp(self) -> crate::SEXP {
150        let vec: Vec<T> = self.into_iter().collect();
151        vec.into_sexp()
152    }
153    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
154        let vec: Vec<T> = self.into_iter().collect();
155        unsafe { vec.into_sexp_unchecked() }
156    }
157}
158
159macro_rules! impl_set_string_into_r {
160    ($(#[$meta:meta])* $set_ty:ident) => {
161        $(#[$meta])*
162        impl IntoR for $set_ty<String> {
163            type Error = crate::into_r_error::IntoRError;
164            fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
165                Ok(self.into_sexp())
166            }
167            unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
168                Ok(unsafe { self.into_sexp_unchecked() })
169            }
170            fn into_sexp(self) -> crate::SEXP {
171                let vec: Vec<String> = self.into_iter().collect();
172                vec.into_sexp()
173            }
174            unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
175                let vec: Vec<String> = self.into_iter().collect();
176                unsafe { vec.into_sexp_unchecked() }
177            }
178        }
179    };
180}
181
182impl_set_string_into_r!(
183    /// Convert `HashSet<String>` to R character vector.
184    HashSet
185);
186impl_set_string_into_r!(
187    /// Convert `BTreeSet<String>` to R character vector.
188    BTreeSet
189);
190// endregion