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 scope = crate::ProtectScope::new();
61        let list = scope.protect_raw(crate::sys::Rf_allocVector(crate::SEXPTYPE::VECSXP, n));
62
63        // Allocate names vector
64        let names = scope.protect_raw(crate::sys::Rf_allocVector(crate::SEXPTYPE::STRSXP, n));
65
66        for (i, (key, value)) in iter.enumerate() {
67            let idx: crate::R_xlen_t = i.try_into().expect("index exceeds isize::MAX");
68            // Set list element
69            list.set_vector_elt(idx, value.into_sexp());
70
71            // Set name
72            let charsxp = str_to_charsxp(&key);
73            names.set_string_elt(idx, charsxp);
74        }
75
76        // Attach names attribute
77        list.set_names(names);
78
79        list
80    }
81}
82
83/// Unchecked version of [`map_to_named_list`].
84unsafe fn map_to_named_list_unchecked<V: IntoR>(
85    iter: impl ExactSizeIterator<Item = (String, V)>,
86) -> crate::SEXP {
87    unsafe {
88        let n: crate::R_xlen_t = iter
89            .len()
90            .try_into()
91            .expect("map length exceeds isize::MAX");
92        let scope = crate::ProtectScope::new();
93        let list = scope.protect_raw(crate::sys::Rf_allocVector_unchecked(
94            crate::SEXPTYPE::VECSXP,
95            n,
96        ));
97
98        let names = scope.protect_raw(crate::sys::Rf_allocVector_unchecked(
99            crate::SEXPTYPE::STRSXP,
100            n,
101        ));
102
103        for (i, (key, value)) in iter.enumerate() {
104            let idx: crate::R_xlen_t = i.try_into().expect("index exceeds isize::MAX");
105            list.set_vector_elt_unchecked(idx, value.into_sexp_unchecked());
106
107            let charsxp = str_to_charsxp_unchecked(&key);
108            names.set_string_elt_unchecked(idx, charsxp);
109        }
110
111        list.set_attr_unchecked(crate::SEXP::names_symbol(), names);
112
113        list
114    }
115}
116
117/// Convert `HashSet<T>` to R vector.
118impl<T> IntoR for HashSet<T>
119where
120    T: crate::RNativeType + Eq + Hash,
121{
122    type Error = std::convert::Infallible;
123    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
124        Ok(self.into_sexp())
125    }
126    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
127        Ok(unsafe { self.into_sexp_unchecked() })
128    }
129    fn into_sexp(self) -> crate::SEXP {
130        let vec: Vec<T> = self.into_iter().collect();
131        vec.into_sexp()
132    }
133    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
134        let vec: Vec<T> = self.into_iter().collect();
135        unsafe { vec.into_sexp_unchecked() }
136    }
137}
138
139/// Convert `BTreeSet<T>` to R vector.
140impl<T> IntoR for BTreeSet<T>
141where
142    T: crate::RNativeType + Ord,
143{
144    type Error = std::convert::Infallible;
145    fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
146        Ok(self.into_sexp())
147    }
148    unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
149        Ok(unsafe { self.into_sexp_unchecked() })
150    }
151    fn into_sexp(self) -> crate::SEXP {
152        let vec: Vec<T> = self.into_iter().collect();
153        vec.into_sexp()
154    }
155    unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
156        let vec: Vec<T> = self.into_iter().collect();
157        unsafe { vec.into_sexp_unchecked() }
158    }
159}
160
161macro_rules! impl_set_string_into_r {
162    ($(#[$meta:meta])* $set_ty:ident) => {
163        $(#[$meta])*
164        impl IntoR for $set_ty<String> {
165            type Error = crate::into_r_error::IntoRError;
166            fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
167                Ok(self.into_sexp())
168            }
169            unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
170                Ok(unsafe { self.into_sexp_unchecked() })
171            }
172            fn into_sexp(self) -> crate::SEXP {
173                let vec: Vec<String> = self.into_iter().collect();
174                vec.into_sexp()
175            }
176            unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
177                let vec: Vec<String> = self.into_iter().collect();
178                unsafe { vec.into_sexp_unchecked() }
179            }
180        }
181    };
182}
183
184impl_set_string_into_r!(
185    /// Convert `HashSet<String>` to R character vector.
186    HashSet
187);
188impl_set_string_into_r!(
189    /// Convert `BTreeSet<String>` to R character vector.
190    BTreeSet
191);
192// endregion