Skip to main content

miniextendr_api/from_r/
cow_and_paths.rs

1//! Cow, PathBuf, OsString, and string collection conversions.
2//!
3//! - `Cow<'_, [T]>` — owned copy of R native vectors
4//! - `Cow<'_, str>` — owned copy of R character scalars
5//! - `PathBuf` / `OsString` — from STRSXP via `String` intermediary
6//! - `HashSet<String>` / `BTreeSet<String>` — string set conversions
7//!
8//! # Tradeoff
9//!
10//! These [`TryFromSexp`] impls reject mismatched
11//! [`SEXPTYPE`](crate::SEXPTYPE)s — there is no looser coercion path for `Cow` / `PathBuf` /
12//! `OsString`. Raw `Cow` conversions own their data and may outlive the source.
13//! [`super::TryFromRoot`] supplies the borrowing path with a root-bound lifetime;
14//! generated wrappers use it for explicit `Cow` parameters.
15
16use std::borrow::Cow;
17use std::collections::{BTreeSet, HashSet};
18use std::ffi::OsString;
19use std::path::PathBuf;
20
21use crate::SEXP;
22use crate::from_r::{SexpError, SexpTypeError, TryFromSexp, charsxp_to_str, map_strsxp_with};
23
24/// Convert an R native vector to an owned `Cow`.
25///
26/// A raw SEXP carries no borrow lifetime. Use [`super::TryFromRoot`] to borrow
27/// without copying; generated wrappers select that path for `Cow<'_, [T]>`.
28impl<T: crate::RNativeType> TryFromSexp for Cow<'_, [T]> {
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::Owned(slice.to_vec()))
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::Owned(slice.to_vec()))
39    }
40}
41
42/// Convert an R character scalar to an owned `Cow`.
43///
44/// The same scalar/NA semantics as `String`. Borrowing requires
45/// [`super::TryFromRoot`], which generated wrappers use for `Cow<'_, str>`.
46impl TryFromSexp for Cow<'_, str> {
47    type Error = SexpError;
48    const CHARACTER_ONLY: bool = true;
49
50    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
51        String::try_from_sexp(sexp).map(Cow::Owned)
52    }
53
54    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
55        unsafe { String::try_from_sexp_unchecked(sexp) }.map(Cow::Owned)
56    }
57}
58
59/// Convert a character vector to owned strings; NA becomes an empty string.
60/// Generated wrappers borrow instead through [`super::TryFromRoot`].
61impl TryFromSexp for Vec<Cow<'_, str>> {
62    type Error = SexpError;
63    const CHARACTER_ONLY: bool = true;
64
65    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
66        Vec::<String>::try_from_sexp(sexp)
67            .map(|values| values.into_iter().map(Cow::Owned).collect())
68    }
69}
70
71/// Convert a character vector to owned strings, preserving NA as `None`.
72/// Generated wrappers borrow instead through [`super::TryFromRoot`].
73impl TryFromSexp for Vec<Option<Cow<'_, str>>> {
74    type Error = SexpError;
75    const CHARACTER_ONLY: bool = true;
76
77    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
78        Vec::<Option<String>>::try_from_sexp(sexp)
79            .map(|values| values.into_iter().map(|v| v.map(Cow::Owned)).collect())
80    }
81}
82
83/// Convert R character vector to `Vec<String>`.
84///
85/// # NA and Encoding Handling
86///
87/// **Warning:** This conversion is lossy for NA values and encoding failures:
88/// - `NA_character_` values are converted to empty string `""`
89/// - Encoding translation failures become empty string `""`
90/// - Invalid UTF-8 (after translation) becomes empty string `""`
91///
92/// If you need to preserve NA semantics, use `Vec<Option<String>>` instead:
93///
94/// ```ignore
95/// let strings: Vec<Option<String>> = sexp.try_into()?;
96/// // NA values will be None, valid strings will be Some(s)
97/// ```
98///
99/// This design choice prioritizes convenience over strict correctness for the
100/// common case where strings are known to be non-NA and properly encoded.
101impl TryFromSexp for Vec<String> {
102    type Error = SexpError;
103    const CHARACTER_ONLY: bool = true;
104
105    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
106        map_strsxp_with(sexp, |charsxp, _i| {
107            let s = if charsxp == SEXP::na_string() {
108                String::new()
109            } else {
110                unsafe { charsxp_to_str(charsxp) }.to_owned()
111            };
112            Ok(s)
113        })
114    }
115}
116
117/// Convert R character vector to `Vec<&str>`.
118///
119/// **Warning:** `NA_character_` values are converted to empty string `""`.
120impl TryFromSexp for Vec<&'static str> {
121    type Error = SexpError;
122    const CHARACTER_ONLY: bool = true;
123
124    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
125        map_strsxp_with(sexp, |charsxp, _i| {
126            if charsxp == SEXP::na_string() || charsxp == SEXP::blank_string() {
127                return Ok("");
128            }
129            Ok(unsafe { charsxp_to_str(charsxp) })
130        })
131    }
132}
133
134/// Convert R character vector to `Vec<Option<&str>>`.
135impl TryFromSexp for Vec<Option<&'static str>> {
136    type Error = SexpError;
137    const CHARACTER_ONLY: bool = true;
138
139    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
140        map_strsxp_with(sexp, |charsxp, _i| {
141            if charsxp == SEXP::na_string() {
142                return Ok(None);
143            }
144            if charsxp == SEXP::blank_string() {
145                return Ok(Some(""));
146            }
147            Ok(Some(unsafe { charsxp_to_str(charsxp) }))
148        })
149    }
150}
151
152macro_rules! impl_set_string_try_from_sexp {
153    ($(#[$meta:meta])* $set_ty:ident) => {
154        $(#[$meta])*
155        impl TryFromSexp for $set_ty<String> {
156            type Error = SexpError;
157            const CHARACTER_ONLY: bool = true;
158
159            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
160                let vec: Vec<String> = TryFromSexp::try_from_sexp(sexp)?;
161                Ok(vec.into_iter().collect())
162            }
163        }
164    };
165}
166
167#[doc = r" Convert R character vector to `HashSet<String>`."]
impl TryFromSexp for HashSet<String> {
    type Error = SexpError;
    const CHARACTER_ONLY: bool = true;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        let vec: Vec<String> = TryFromSexp::try_from_sexp(sexp)?;
        Ok(vec.into_iter().collect())
    }
}impl_set_string_try_from_sexp!(
168    /// Convert R character vector to `HashSet<String>`.
169    HashSet
170);
171#[doc = r" Convert R character vector to `BTreeSet<String>`."]
impl TryFromSexp for BTreeSet<String> {
    type Error = SexpError;
    const CHARACTER_ONLY: bool = true;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        let vec: Vec<String> = TryFromSexp::try_from_sexp(sexp)?;
        Ok(vec.into_iter().collect())
    }
}impl_set_string_try_from_sexp!(
172    /// Convert R character vector to `BTreeSet<String>`.
173    BTreeSet
174);
175// endregion
176
177// region: String-wrapper type conversions (PathBuf, OsString)
178
179/// Generate TryFromSexp impls for types that are `From<String>` (scalar, Option,
180/// Vec, `Vec<Option>`). Used for PathBuf and OsString which delegate to String conversion.
181macro_rules! impl_string_wrapper_try_from_sexp {
182    (
183        $(#[$scalar_meta:meta])*
184        scalar: $ty:ty;
185        $(#[$option_meta:meta])*
186        option: $ty2:ty;
187        $(#[$vec_meta:meta])*
188        vec: $ty3:ty;
189        $(#[$vec_option_meta:meta])*
190        vec_option: $ty4:ty;
191    ) => {
192        $(#[$scalar_meta])*
193        impl TryFromSexp for $ty {
194            type Error = SexpError;
195            const CHARACTER_ONLY: bool = true;
196
197            #[inline]
198            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
199                let s: String = TryFromSexp::try_from_sexp(sexp)?;
200                Ok(<$ty>::from(s))
201            }
202
203            #[inline]
204            unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
205                let s: String = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
206                Ok(<$ty>::from(s))
207            }
208        }
209
210        $(#[$option_meta])*
211        impl TryFromSexp for Option<$ty> {
212            type Error = SexpError;
213            const CHARACTER_ONLY: bool = true;
214
215            #[inline]
216            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
217                let opt: Option<String> = TryFromSexp::try_from_sexp(sexp)?;
218                Ok(opt.map(<$ty>::from))
219            }
220
221            #[inline]
222            unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
223                let opt: Option<String> = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
224                Ok(opt.map(<$ty>::from))
225            }
226        }
227
228        $(#[$vec_meta])*
229        impl TryFromSexp for Vec<$ty> {
230            type Error = SexpError;
231            const CHARACTER_ONLY: bool = true;
232
233            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
234                let vec: Vec<String> = TryFromSexp::try_from_sexp(sexp)?;
235                Ok(vec.into_iter().map(<$ty>::from).collect())
236            }
237
238            unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
239                let vec: Vec<String> = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
240                Ok(vec.into_iter().map(<$ty>::from).collect())
241            }
242        }
243
244        $(#[$vec_option_meta])*
245        impl TryFromSexp for Vec<Option<$ty>> {
246            type Error = SexpError;
247            const CHARACTER_ONLY: bool = true;
248
249            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
250                let vec: Vec<Option<String>> = TryFromSexp::try_from_sexp(sexp)?;
251                Ok(vec.into_iter().map(|opt| opt.map(<$ty>::from)).collect())
252            }
253
254            unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
255                let vec: Vec<Option<String>> = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
256                Ok(vec.into_iter().map(|opt| opt.map(<$ty>::from)).collect())
257            }
258        }
259    };
260}
261
262#[doc = r" Convert R character scalar (STRSXP of length 1) to `PathBuf`."]
#[doc = r""]
#[doc = r" # NA Handling"]
#[doc = r""]
#[doc =
r#" **Warning:** `NA_character_` is converted to empty path `""`. This is lossy!"#]
#[doc =
r" If you need to distinguish between NA and empty strings, use `Option<PathBuf>` instead."]
impl TryFromSexp for PathBuf {
    type Error = SexpError;
    const CHARACTER_ONLY: bool = true;
    #[inline]
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        let s: String = TryFromSexp::try_from_sexp(sexp)?;
        Ok(<PathBuf>::from(s))
    }
    #[inline]
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        let s: String =
            unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
        Ok(<PathBuf>::from(s))
    }
}
#[doc =
r" NA-aware PathBuf conversion: returns `None` for `NA_character_` or `NULL`."]
impl TryFromSexp for Option<PathBuf> {
    type Error = SexpError;
    const CHARACTER_ONLY: bool = true;
    #[inline]
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        let opt: Option<String> = TryFromSexp::try_from_sexp(sexp)?;
        Ok(opt.map(<PathBuf>::from))
    }
    #[inline]
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        let opt: Option<String> =
            unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
        Ok(opt.map(<PathBuf>::from))
    }
}
#[doc = r" Convert R character vector (STRSXP) to `Vec<PathBuf>`."]
#[doc = r""]
#[doc = r" # NA Handling"]
#[doc = r""]
#[doc =
r" **Warning:** `NA_character_` elements are converted to empty paths."]
#[doc = r" Use `Vec<Option<PathBuf>>` if you need to preserve NA values."]
impl TryFromSexp for Vec<PathBuf> {
    type Error = SexpError;
    const CHARACTER_ONLY: bool = true;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        let vec: Vec<String> = TryFromSexp::try_from_sexp(sexp)?;
        Ok(vec.into_iter().map(<PathBuf>::from).collect())
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        let vec: Vec<String> =
            unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
        Ok(vec.into_iter().map(<PathBuf>::from).collect())
    }
}
#[doc =
r" Convert R character vector (STRSXP) to `Vec<Option<PathBuf>>` with NA support."]
#[doc = r""]
#[doc = r" `NA_character_` elements are converted to `None`."]
impl TryFromSexp for Vec<Option<PathBuf>> {
    type Error = SexpError;
    const CHARACTER_ONLY: bool = true;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        let vec: Vec<Option<String>> = TryFromSexp::try_from_sexp(sexp)?;
        Ok(vec.into_iter().map(|opt| opt.map(<PathBuf>::from)).collect())
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        let vec: Vec<Option<String>> =
            unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
        Ok(vec.into_iter().map(|opt| opt.map(<PathBuf>::from)).collect())
    }
}impl_string_wrapper_try_from_sexp!(
263    /// Convert R character scalar (STRSXP of length 1) to `PathBuf`.
264    ///
265    /// # NA Handling
266    ///
267    /// **Warning:** `NA_character_` is converted to empty path `""`. This is lossy!
268    /// If you need to distinguish between NA and empty strings, use `Option<PathBuf>` instead.
269    scalar: PathBuf;
270    /// NA-aware PathBuf conversion: returns `None` for `NA_character_` or `NULL`.
271    option: PathBuf;
272    /// Convert R character vector (STRSXP) to `Vec<PathBuf>`.
273    ///
274    /// # NA Handling
275    ///
276    /// **Warning:** `NA_character_` elements are converted to empty paths.
277    /// Use `Vec<Option<PathBuf>>` if you need to preserve NA values.
278    vec: PathBuf;
279    /// Convert R character vector (STRSXP) to `Vec<Option<PathBuf>>` with NA support.
280    ///
281    /// `NA_character_` elements are converted to `None`.
282    vec_option: PathBuf;
283);
284
285#[doc = r" Convert R character scalar (STRSXP of length 1) to `OsString`."]
#[doc = r""]
#[doc =
r" Since R strings are converted to UTF-8, the resulting `OsString` contains"]
#[doc = r" valid UTF-8 data."]
#[doc = r""]
#[doc = r" # NA Handling"]
#[doc = r""]
#[doc =
r" **Warning:** `NA_character_` is converted to empty string. This is lossy!"]
#[doc =
r" If you need to distinguish between NA and empty strings, use `Option<OsString>` instead."]
impl TryFromSexp for OsString {
    type Error = SexpError;
    const CHARACTER_ONLY: bool = true;
    #[inline]
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        let s: String = TryFromSexp::try_from_sexp(sexp)?;
        Ok(<OsString>::from(s))
    }
    #[inline]
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        let s: String =
            unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
        Ok(<OsString>::from(s))
    }
}
#[doc =
r" NA-aware OsString conversion: returns `None` for `NA_character_` or `NULL`."]
impl TryFromSexp for Option<OsString> {
    type Error = SexpError;
    const CHARACTER_ONLY: bool = true;
    #[inline]
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        let opt: Option<String> = TryFromSexp::try_from_sexp(sexp)?;
        Ok(opt.map(<OsString>::from))
    }
    #[inline]
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        let opt: Option<String> =
            unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
        Ok(opt.map(<OsString>::from))
    }
}
#[doc = r" Convert R character vector (STRSXP) to `Vec<OsString>`."]
#[doc = r""]
#[doc = r" # NA Handling"]
#[doc = r""]
#[doc =
r" **Warning:** `NA_character_` elements are converted to empty strings."]
#[doc = r" Use `Vec<Option<OsString>>` if you need to preserve NA values."]
impl TryFromSexp for Vec<OsString> {
    type Error = SexpError;
    const CHARACTER_ONLY: bool = true;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        let vec: Vec<String> = TryFromSexp::try_from_sexp(sexp)?;
        Ok(vec.into_iter().map(<OsString>::from).collect())
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        let vec: Vec<String> =
            unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
        Ok(vec.into_iter().map(<OsString>::from).collect())
    }
}
#[doc =
r" Convert R character vector (STRSXP) to `Vec<Option<OsString>>` with NA support."]
#[doc = r""]
#[doc = r" `NA_character_` elements are converted to `None`."]
impl TryFromSexp for Vec<Option<OsString>> {
    type Error = SexpError;
    const CHARACTER_ONLY: bool = true;
    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
        let vec: Vec<Option<String>> = TryFromSexp::try_from_sexp(sexp)?;
        Ok(vec.into_iter().map(|opt| opt.map(<OsString>::from)).collect())
    }
    unsafe fn try_from_sexp_unchecked(sexp: SEXP)
        -> Result<Self, Self::Error> {
        let vec: Vec<Option<String>> =
            unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
        Ok(vec.into_iter().map(|opt| opt.map(<OsString>::from)).collect())
    }
}impl_string_wrapper_try_from_sexp!(
286    /// Convert R character scalar (STRSXP of length 1) to `OsString`.
287    ///
288    /// Since R strings are converted to UTF-8, the resulting `OsString` contains
289    /// valid UTF-8 data.
290    ///
291    /// # NA Handling
292    ///
293    /// **Warning:** `NA_character_` is converted to empty string. This is lossy!
294    /// If you need to distinguish between NA and empty strings, use `Option<OsString>` instead.
295    scalar: OsString;
296    /// NA-aware OsString conversion: returns `None` for `NA_character_` or `NULL`.
297    option: OsString;
298    /// Convert R character vector (STRSXP) to `Vec<OsString>`.
299    ///
300    /// # NA Handling
301    ///
302    /// **Warning:** `NA_character_` elements are converted to empty strings.
303    /// Use `Vec<Option<OsString>>` if you need to preserve NA values.
304    vec: OsString;
305    /// Convert R character vector (STRSXP) to `Vec<Option<OsString>>` with NA support.
306    ///
307    /// `NA_character_` elements are converted to `None`.
308    vec_option: OsString;
309);
310// endregion