Skip to main content

miniextendr_api/from_r/
cow_and_paths.rs

1//! Cow, PathBuf, OsString, and string collection conversions.
2//!
3//! - `Cow<'static, [T]>` — zero-copy borrow of R native vectors
4//! - `Cow<'static, str>` — zero-copy borrow of R character scalars
5//! - `PathBuf` / `OsString` — from STRSXP via `String` intermediary
6//! - `HashSet<String>` / `BTreeSet<String>` — string set conversions
7
8use std::borrow::Cow;
9use std::collections::{BTreeSet, HashSet};
10use std::ffi::OsString;
11use std::path::PathBuf;
12
13use crate::ffi::{SEXP, SEXPTYPE, SexpExt};
14use crate::from_r::{SexpError, SexpTypeError, TryFromSexp, charsxp_to_cow, charsxp_to_str};
15
16/// Blanket impl: Convert R vector to `Cow<'static, [T]>` where T: RNativeType.
17///
18/// Returns `Cow::Borrowed` — the slice points directly into R's SEXP data with
19/// no copy. The `'static` lifetime is valid for the duration of the `.Call`
20/// invocation (R protects the SEXP from GC while Rust code is running).
21///
22/// **Important:** Do not send the borrowed `Cow` to another thread or store it
23/// past the `.Call` return — the underlying R memory is only valid while
24/// R's protection stack guards this SEXP.
25impl<T> TryFromSexp for Cow<'static, [T]>
26where
27    T: crate::ffi::RNativeType + Copy + Clone,
28{
29    type Error = SexpTypeError;
30
31    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
32        let slice: &[T] = TryFromSexp::try_from_sexp(sexp)?;
33        Ok(Cow::Borrowed(slice))
34    }
35
36    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
37        let slice: &[T] = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
38        Ok(Cow::Borrowed(slice))
39    }
40}
41
42/// Convert R character scalar to `Cow<'static, str>`.
43///
44/// Returns `Cow::Borrowed` — the `&str` points directly into R's CHARSXP data
45/// via `R_CHAR` + `LENGTH` (O(1), no strlen). No allocation or copy occurs.
46/// The `'static` lifetime is valid for the duration of the `.Call` invocation.
47///
48/// This delegates to the `&'static str` impl (which uses `charsxp_to_str`),
49/// giving the same zero-copy behavior. Use `Cow` when your code may need to
50/// mutate the string later — `to_mut()` will copy-on-write at that point.
51impl TryFromSexp for Cow<'static, str> {
52    type Error = SexpError;
53
54    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
55        let s: &'static str = TryFromSexp::try_from_sexp(sexp)?;
56        Ok(Cow::Borrowed(s))
57    }
58
59    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
60        let s: &'static str = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
61        Ok(Cow::Borrowed(s))
62    }
63}
64
65/// Convert R character vector to `Vec<Cow<'static, str>>` — zero-copy per element.
66///
67/// Each element borrows directly from R's CHARSXP data when UTF-8 (the common case).
68/// Non-UTF-8 elements fall back to `Rf_translateCharUTF8` (copies only those).
69///
70/// # NA Handling
71///
72/// **Warning:** `NA_character_` is converted to `Cow::Borrowed("")`. This is lossy!
73/// Use `Vec<Option<Cow<'static, str>>>` to distinguish NA from empty strings.
74impl TryFromSexp for Vec<Cow<'static, str>> {
75    type Error = SexpError;
76
77    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
78        let actual = sexp.type_of();
79        if actual != SEXPTYPE::STRSXP {
80            return Err(SexpTypeError {
81                expected: SEXPTYPE::STRSXP,
82                actual,
83            }
84            .into());
85        }
86
87        let len = sexp.len();
88        let mut result = Vec::with_capacity(len);
89
90        for i in 0..len {
91            let charsxp = sexp.string_elt(i as crate::ffi::R_xlen_t);
92            if charsxp == SEXP::na_string() || charsxp == SEXP::blank_string() {
93                result.push(Cow::Borrowed(""));
94            } else {
95                result.push(unsafe { charsxp_to_cow(charsxp) });
96            }
97        }
98
99        Ok(result)
100    }
101}
102
103/// Convert R character vector to `Vec<Option<Cow<'static, str>>>` — zero-copy, NA-aware.
104///
105/// `NA_character_` → `None`, valid strings → `Some(Cow::Borrowed(&str))`.
106impl TryFromSexp for Vec<Option<Cow<'static, str>>> {
107    type Error = SexpError;
108
109    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
110        let actual = sexp.type_of();
111        if actual != SEXPTYPE::STRSXP {
112            return Err(SexpTypeError {
113                expected: SEXPTYPE::STRSXP,
114                actual,
115            }
116            .into());
117        }
118
119        let len = sexp.len();
120        let mut result = Vec::with_capacity(len);
121
122        for i in 0..len {
123            let charsxp = sexp.string_elt(i as crate::ffi::R_xlen_t);
124            if charsxp == SEXP::na_string() {
125                result.push(None);
126            } else {
127                // charsxp_to_cow returns Cow::Borrowed("") for R_BlankString-equivalent
128                result.push(Some(unsafe { charsxp_to_cow(charsxp) }));
129            }
130        }
131
132        Ok(result)
133    }
134}
135
136/// Convert R character vector to `Box<[Cow<'static, str>]>` — zero-copy per element.
137///
138/// **Warning:** `NA_character_` values are converted to `Cow::Borrowed("")`.
139impl TryFromSexp for Box<[Cow<'static, str>]> {
140    type Error = SexpError;
141
142    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
143        let vec: Vec<Cow<'static, str>> = TryFromSexp::try_from_sexp(sexp)?;
144        Ok(vec.into_boxed_slice())
145    }
146}
147
148/// Convert R character vector to `Vec<String>`.
149///
150/// # NA and Encoding Handling
151///
152/// **Warning:** This conversion is lossy for NA values and encoding failures:
153/// - `NA_character_` values are converted to empty string `""`
154/// - Encoding translation failures become empty string `""`
155/// - Invalid UTF-8 (after translation) becomes empty string `""`
156///
157/// If you need to preserve NA semantics, use `Vec<Option<String>>` instead:
158///
159/// ```ignore
160/// let strings: Vec<Option<String>> = sexp.try_into()?;
161/// // NA values will be None, valid strings will be Some(s)
162/// ```
163///
164/// This design choice prioritizes convenience over strict correctness for the
165/// common case where strings are known to be non-NA and properly encoded.
166impl TryFromSexp for Vec<String> {
167    type Error = SexpError;
168
169    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
170        use crate::ffi::Rf_translateCharUTF8;
171
172        let actual = sexp.type_of();
173        if actual != SEXPTYPE::STRSXP {
174            return Err(SexpTypeError {
175                expected: SEXPTYPE::STRSXP,
176                actual,
177            }
178            .into());
179        }
180
181        let len = sexp.len();
182        let mut result = Vec::with_capacity(len);
183
184        for i in 0..len {
185            let charsxp = sexp.string_elt(i as crate::ffi::R_xlen_t);
186            let s = if charsxp == SEXP::na_string() {
187                String::new()
188            } else {
189                let c_str = unsafe { Rf_translateCharUTF8(charsxp) };
190                if c_str.is_null() {
191                    String::new()
192                } else {
193                    unsafe { std::ffi::CStr::from_ptr(c_str) }
194                        .to_str()
195                        .unwrap_or("")
196                        .to_owned()
197                }
198            };
199            result.push(s);
200        }
201
202        Ok(result)
203    }
204}
205
206/// Convert R character vector to `Box<[String]>`.
207///
208/// **Warning:** `NA_character_` values are converted to empty string `""`.
209impl TryFromSexp for Box<[String]> {
210    type Error = SexpError;
211
212    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
213        let vec: Vec<String> = TryFromSexp::try_from_sexp(sexp)?;
214        Ok(vec.into_boxed_slice())
215    }
216}
217
218/// Convert R character vector to `Vec<&str>`.
219///
220/// **Warning:** `NA_character_` values are converted to empty string `""`.
221impl TryFromSexp for Vec<&'static str> {
222    type Error = SexpError;
223
224    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
225        let actual = sexp.type_of();
226        if actual != SEXPTYPE::STRSXP {
227            return Err(SexpTypeError {
228                expected: SEXPTYPE::STRSXP,
229                actual,
230            }
231            .into());
232        }
233
234        let len = sexp.len();
235        let mut result = Vec::with_capacity(len);
236
237        for i in 0..len {
238            let charsxp = sexp.string_elt(i as crate::ffi::R_xlen_t);
239            if charsxp == SEXP::na_string() {
240                result.push("");
241                continue;
242            }
243            if charsxp == SEXP::blank_string() {
244                result.push("");
245                continue;
246            }
247            result.push(unsafe { charsxp_to_str(charsxp) });
248        }
249
250        Ok(result)
251    }
252}
253
254/// Convert R character vector to `Vec<Option<&str>>`.
255impl TryFromSexp for Vec<Option<&'static str>> {
256    type Error = SexpError;
257
258    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
259        let actual = sexp.type_of();
260        if actual != SEXPTYPE::STRSXP {
261            return Err(SexpTypeError {
262                expected: SEXPTYPE::STRSXP,
263                actual,
264            }
265            .into());
266        }
267
268        let len = sexp.len();
269        let mut result = Vec::with_capacity(len);
270
271        for i in 0..len {
272            let charsxp = sexp.string_elt(i as crate::ffi::R_xlen_t);
273            if charsxp == SEXP::na_string() {
274                result.push(None);
275                continue;
276            }
277            if charsxp == SEXP::blank_string() {
278                result.push(Some(""));
279                continue;
280            }
281            result.push(Some(unsafe { charsxp_to_str(charsxp) }));
282        }
283
284        Ok(result)
285    }
286}
287
288macro_rules! impl_set_string_try_from_sexp {
289    ($(#[$meta:meta])* $set_ty:ident) => {
290        $(#[$meta])*
291        impl TryFromSexp for $set_ty<String> {
292            type Error = SexpError;
293
294            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
295                let vec: Vec<String> = TryFromSexp::try_from_sexp(sexp)?;
296                Ok(vec.into_iter().collect())
297            }
298        }
299    };
300}
301
302impl_set_string_try_from_sexp!(
303    /// Convert R character vector to `HashSet<String>`.
304    HashSet
305);
306impl_set_string_try_from_sexp!(
307    /// Convert R character vector to `BTreeSet<String>`.
308    BTreeSet
309);
310// endregion
311
312// region: String-wrapper type conversions (PathBuf, OsString)
313
314/// Generate TryFromSexp impls for types that are `From<String>` (scalar, Option,
315/// Vec, Vec<Option>). Used for PathBuf and OsString which delegate to String conversion.
316macro_rules! impl_string_wrapper_try_from_sexp {
317    (
318        $(#[$scalar_meta:meta])*
319        scalar: $ty:ty;
320        $(#[$option_meta:meta])*
321        option: $ty2:ty;
322        $(#[$vec_meta:meta])*
323        vec: $ty3:ty;
324        $(#[$vec_option_meta:meta])*
325        vec_option: $ty4:ty;
326    ) => {
327        $(#[$scalar_meta])*
328        impl TryFromSexp for $ty {
329            type Error = SexpError;
330
331            #[inline]
332            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
333                let s: String = TryFromSexp::try_from_sexp(sexp)?;
334                Ok(<$ty>::from(s))
335            }
336
337            #[inline]
338            unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
339                let s: String = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
340                Ok(<$ty>::from(s))
341            }
342        }
343
344        $(#[$option_meta])*
345        impl TryFromSexp for Option<$ty> {
346            type Error = SexpError;
347
348            #[inline]
349            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
350                let opt: Option<String> = TryFromSexp::try_from_sexp(sexp)?;
351                Ok(opt.map(<$ty>::from))
352            }
353
354            #[inline]
355            unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
356                let opt: Option<String> = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
357                Ok(opt.map(<$ty>::from))
358            }
359        }
360
361        $(#[$vec_meta])*
362        impl TryFromSexp for Vec<$ty> {
363            type Error = SexpError;
364
365            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
366                let vec: Vec<String> = TryFromSexp::try_from_sexp(sexp)?;
367                Ok(vec.into_iter().map(<$ty>::from).collect())
368            }
369
370            unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
371                let vec: Vec<String> = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
372                Ok(vec.into_iter().map(<$ty>::from).collect())
373            }
374        }
375
376        $(#[$vec_option_meta])*
377        impl TryFromSexp for Vec<Option<$ty>> {
378            type Error = SexpError;
379
380            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
381                let vec: Vec<Option<String>> = TryFromSexp::try_from_sexp(sexp)?;
382                Ok(vec.into_iter().map(|opt| opt.map(<$ty>::from)).collect())
383            }
384
385            unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
386                let vec: Vec<Option<String>> = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
387                Ok(vec.into_iter().map(|opt| opt.map(<$ty>::from)).collect())
388            }
389        }
390    };
391}
392
393impl_string_wrapper_try_from_sexp!(
394    /// Convert R character scalar (STRSXP of length 1) to `PathBuf`.
395    ///
396    /// # NA Handling
397    ///
398    /// **Warning:** `NA_character_` is converted to empty path `""`. This is lossy!
399    /// If you need to distinguish between NA and empty strings, use `Option<PathBuf>` instead.
400    scalar: PathBuf;
401    /// NA-aware PathBuf conversion: returns `None` for `NA_character_` or `NULL`.
402    option: PathBuf;
403    /// Convert R character vector (STRSXP) to `Vec<PathBuf>`.
404    ///
405    /// # NA Handling
406    ///
407    /// **Warning:** `NA_character_` elements are converted to empty paths.
408    /// Use `Vec<Option<PathBuf>>` if you need to preserve NA values.
409    vec: PathBuf;
410    /// Convert R character vector (STRSXP) to `Vec<Option<PathBuf>>` with NA support.
411    ///
412    /// `NA_character_` elements are converted to `None`.
413    vec_option: PathBuf;
414);
415
416impl_string_wrapper_try_from_sexp!(
417    /// Convert R character scalar (STRSXP of length 1) to `OsString`.
418    ///
419    /// Since R strings are converted to UTF-8, the resulting `OsString` contains
420    /// valid UTF-8 data.
421    ///
422    /// # NA Handling
423    ///
424    /// **Warning:** `NA_character_` is converted to empty string. This is lossy!
425    /// If you need to distinguish between NA and empty strings, use `Option<OsString>` instead.
426    scalar: OsString;
427    /// NA-aware OsString conversion: returns `None` for `NA_character_` or `NULL`.
428    option: OsString;
429    /// Convert R character vector (STRSXP) to `Vec<OsString>`.
430    ///
431    /// # NA Handling
432    ///
433    /// **Warning:** `NA_character_` elements are converted to empty strings.
434    /// Use `Vec<Option<OsString>>` if you need to preserve NA values.
435    vec: OsString;
436    /// Convert R character vector (STRSXP) to `Vec<Option<OsString>>` with NA support.
437    ///
438    /// `NA_character_` elements are converted to `None`.
439    vec_option: OsString;
440);
441// endregion