Skip to main content

miniextendr_api/from_r/
na_vectors.rs

1//! NA-aware vector conversions (`Vec<Option<T>>`, `Box<[Option<T>]>`).
2//!
3//! **The `SEXPTYPE` literals here are the source of truth (#882).** The macro
4//! that generates the native-type impls is passed the tag as `$sexptype` from the
5//! caller (where the concrete element type *is* known); the hand-written logical
6//! impls use `LGLSXP` because `bool`/`Rboolean` are not `RNativeType` (R logicals
7//! are `i32`), and the string/raw impls use `STRSXP`/`RAWSXP` literals because
8//! those element types have no `RNativeType` collapse — leave them.
9//!
10//! Maps R's NA values to `None` and non-NA values to `Some(v)`.
11//! Covers native types (i32, f64, u8), logical (bool, Rboolean, RLogical),
12//! string (`Option<String>`), complex (`Option<Rcomplex>`), and coerced
13//! numeric types (`Option<i64>`, `Option<u64>`, etc.).
14//!
15//! # Tradeoff
16//!
17//! Use the NA-unaware sibling impls (`Vec<T>`, `Box<[T]>`) when R guarantees
18//! no NA — they're cheaper (no per-element `Option` discriminant) and reject
19//! NA at conversion time. Failure mode of binding plain `Vec<i32>` when the
20//! R caller can pass `NA_integer_`: a single `NA` element silently round-trips
21//! as `i32::MIN`, the R NA sentinel, which is a footgun for arithmetic
22//! downstream.
23//!
24//! Outbound counterpart: `Vec<Option<T>>` impls in [`crate::into_r`].
25
26use crate::coerce::TryCoerce;
27use crate::from_r::{
28    SexpError, SexpNaError, SexpTypeError, TryFromSexp, charsxp_to_str, coerce_value,
29    from_numeric_vec_with, is_na_real, map_strsxp_with, r_slice,
30};
31use crate::{RLogical, Rboolean, SEXP, SEXPTYPE, SexpExt};
32
33/// Macro for NA-aware `R vector → Vec<Option<T>>` conversions.
34macro_rules! impl_vec_option_try_from_sexp {
35    ($t:ty, $sexptype:ident, $dataptr:ident, $is_na:expr) => {
36        impl TryFromSexp for Vec<Option<$t>> {
37            type Error = SexpError;
38
39            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
40                let actual = sexp.type_of();
41                if actual != SEXPTYPE::$sexptype {
42                    return Err(SexpTypeError {
43                        expected: SEXPTYPE::$sexptype,
44                        actual,
45                    }
46                    .into());
47                }
48
49                let len = sexp.len();
50                let ptr = unsafe { crate::sys::$dataptr(sexp) };
51                let slice = unsafe { r_slice(ptr, len) };
52
53                Ok(slice
54                    .iter()
55                    .map(|&v| if $is_na(v) { None } else { Some(v) })
56                    .collect())
57            }
58        }
59    };
60}
61
62impl_vec_option_try_from_sexp!(f64, REALSXP, REAL, is_na_real);
63impl_vec_option_try_from_sexp!(i32, INTSXP, INTEGER, |v: i32| v == i32::MIN);
64
65/// Convert R logical vector (LGLSXP) to `Vec<Option<bool>>` with NA support.
66impl TryFromSexp for Vec<Option<bool>> {
67    type Error = SexpError;
68
69    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
70        let actual = sexp.type_of();
71        if actual != SEXPTYPE::LGLSXP {
72            return Err(SexpTypeError {
73                expected: SEXPTYPE::LGLSXP,
74                actual,
75            }
76            .into());
77        }
78
79        let slice: &[RLogical] = unsafe { sexp.as_slice() };
80
81        Ok(slice.iter().map(|v| v.to_option_bool()).collect())
82    }
83
84    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
85        let actual = sexp.type_of();
86        if actual != SEXPTYPE::LGLSXP {
87            return Err(SexpTypeError {
88                expected: SEXPTYPE::LGLSXP,
89                actual,
90            }
91            .into());
92        }
93
94        let slice: &[RLogical] = unsafe { sexp.as_slice_unchecked() };
95
96        Ok(slice.iter().map(|v| v.to_option_bool()).collect())
97    }
98}
99
100/// Convert R logical vector (LGLSXP) to `Vec<Rboolean>` (errors on NA).
101impl TryFromSexp for Vec<Rboolean> {
102    type Error = SexpError;
103
104    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
105        let actual = sexp.type_of();
106        if actual != SEXPTYPE::LGLSXP {
107            return Err(SexpTypeError {
108                expected: SEXPTYPE::LGLSXP,
109                actual,
110            }
111            .into());
112        }
113
114        let slice: &[RLogical] = unsafe { sexp.as_slice() };
115
116        slice
117            .iter()
118            .map(|v| match v.to_option_bool() {
119                Some(false) => Ok(Rboolean::FALSE),
120                Some(true) => Ok(Rboolean::TRUE),
121                None => Err(SexpNaError {
122                    sexp_type: SEXPTYPE::LGLSXP,
123                }
124                .into()),
125            })
126            .collect()
127    }
128}
129
130/// Convert R logical vector (LGLSXP) to `Vec<Option<Rboolean>>` with NA support.
131impl TryFromSexp for Vec<Option<Rboolean>> {
132    type Error = SexpError;
133
134    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
135        let actual = sexp.type_of();
136        if actual != SEXPTYPE::LGLSXP {
137            return Err(SexpTypeError {
138                expected: SEXPTYPE::LGLSXP,
139                actual,
140            }
141            .into());
142        }
143
144        let slice: &[RLogical] = unsafe { sexp.as_slice() };
145
146        Ok(slice
147            .iter()
148            .map(|v| match v.to_option_bool() {
149                Some(false) => Some(Rboolean::FALSE),
150                Some(true) => Some(Rboolean::TRUE),
151                None => None,
152            })
153            .collect())
154    }
155}
156
157/// Convert R logical vector (LGLSXP) to `Vec<Logical>` (ALTREP-compatible).
158///
159/// This converts R's logical vector to a vector of [`Logical`](crate::altrep_data::Logical) values,
160/// which is the native representation used by ALTREP logical vectors.
161/// Unlike `Vec<bool>`, this preserves NA values.
162impl TryFromSexp for Vec<crate::altrep_data::Logical> {
163    type Error = SexpError;
164
165    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
166        let actual = sexp.type_of();
167        if actual != SEXPTYPE::LGLSXP {
168            return Err(SexpTypeError {
169                expected: SEXPTYPE::LGLSXP,
170                actual,
171            }
172            .into());
173        }
174
175        let slice: &[RLogical] = unsafe { sexp.as_slice() };
176
177        Ok(slice
178            .iter()
179            .map(|&v| crate::altrep_data::Logical::from(v))
180            .collect())
181    }
182}
183
184/// Convert R logical vector (LGLSXP) to `Vec<Option<RLogical>>` with NA support.
185impl TryFromSexp for Vec<Option<RLogical>> {
186    type Error = SexpError;
187
188    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
189        let actual = sexp.type_of();
190        if actual != SEXPTYPE::LGLSXP {
191            return Err(SexpTypeError {
192                expected: SEXPTYPE::LGLSXP,
193                actual,
194            }
195            .into());
196        }
197
198        let slice: &[RLogical] = unsafe { sexp.as_slice() };
199
200        Ok(slice
201            .iter()
202            .map(|v| if v.is_na() { None } else { Some(*v) })
203            .collect())
204    }
205}
206
207/// Convert R character vector (STRSXP) to `Vec<Option<String>>` with NA support.
208///
209/// `NA_character_` elements are converted to `None`.
210impl TryFromSexp for Vec<Option<String>> {
211    type Error = SexpError;
212
213    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
214        map_strsxp_with(sexp, |charsxp, _i| {
215            if charsxp == SEXP::na_string() {
216                Ok(None)
217            } else {
218                Ok(Some(unsafe { charsxp_to_str(charsxp) }.to_owned()))
219            }
220        })
221    }
222}
223
224/// Convert R raw vector (RAWSXP) to `Vec<Option<u8>>`.
225impl TryFromSexp for Vec<Option<u8>> {
226    type Error = SexpError;
227
228    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
229        let actual = sexp.type_of();
230        if actual != SEXPTYPE::RAWSXP {
231            return Err(SexpTypeError {
232                expected: SEXPTYPE::RAWSXP,
233                actual,
234            }
235            .into());
236        }
237
238        let slice: &[u8] = unsafe { sexp.as_slice() };
239
240        Ok(slice.iter().map(|&v| Some(v)).collect())
241    }
242}
243
244#[inline]
245fn try_from_sexp_numeric_option_vec<T>(sexp: SEXP) -> Result<Vec<Option<T>>, SexpError>
246where
247    i32: TryCoerce<T>,
248    f64: TryCoerce<T>,
249    u8: TryCoerce<T>,
250    <i32 as TryCoerce<T>>::Error: std::fmt::Debug,
251    <f64 as TryCoerce<T>>::Error: std::fmt::Debug,
252    <u8 as TryCoerce<T>>::Error: std::fmt::Debug,
253{
254    from_numeric_vec_with(
255        sexp,
256        |v: i32| {
257            if v == crate::altrep_traits::NA_INTEGER {
258                Ok(None)
259            } else {
260                coerce_value(v).map(Some)
261            }
262        },
263        |v: f64| {
264            if is_na_real(v) {
265                Ok(None)
266            } else {
267                coerce_value(v).map(Some)
268            }
269        },
270        // RAWSXP has no NA in R: every byte is a value.
271        |v: u8| coerce_value(v).map(Some),
272        |v: RLogical| {
273            if v.is_na() {
274                Ok(None)
275            } else {
276                coerce_value(v.to_i32()).map(Some)
277            }
278        },
279    )
280}
281
282macro_rules! impl_vec_option_try_from_sexp_numeric {
283    ($t:ty) => {
284        impl TryFromSexp for Vec<Option<$t>> {
285            type Error = SexpError;
286
287            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
288                try_from_sexp_numeric_option_vec(sexp)
289            }
290
291            unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
292                try_from_sexp_numeric_option_vec(sexp)
293            }
294        }
295    };
296}
297
298impl_vec_option_try_from_sexp_numeric!(i8);
299impl_vec_option_try_from_sexp_numeric!(i16);
300impl_vec_option_try_from_sexp_numeric!(u16);
301impl_vec_option_try_from_sexp_numeric!(u32);
302impl_vec_option_try_from_sexp_numeric!(i64);
303impl_vec_option_try_from_sexp_numeric!(u64);
304impl_vec_option_try_from_sexp_numeric!(isize);
305impl_vec_option_try_from_sexp_numeric!(usize);
306impl_vec_option_try_from_sexp_numeric!(f32);
307// endregion