Skip to main content

miniextendr_api/
strvec.rs

1//! Thin wrapper around R character vector (`STRSXP`).
2//!
3//! Provides safe construction and element insertion for string vectors.
4
5use std::borrow::Cow;
6use std::marker::PhantomData;
7
8use crate::SEXPTYPE::STRSXP;
9use crate::from_r::{SexpError, SexpTypeError, TryFromSexp, charsxp_to_cow, charsxp_to_str};
10use crate::gc_protect::{OwnedProtect, ProtectScope, Protected};
11use crate::into_r::IntoR;
12use crate::{SEXP, SexpExt};
13
14/// Borrowed view over an R character vector (`STRSXP`).
15///
16/// The `'a` lifetime leashes every `&str` this view hands out to the window in
17/// which the underlying `STRSXP` stays GC-reachable. At a `#[miniextendr]`
18/// boundary, write `StrVec<'_>`: R protects `.Call` arguments for the call's
19/// duration, so the elided lifetime is bounded by the call and the borrows
20/// cannot escape into an `ExternalPtr`, a global, or another thread — doing so
21/// is a compile error (the `&str` would have to outlive `'a`). Callers that own
22/// the rooting obligation reach for the `unsafe` `*_static` family instead.
23///
24/// `Copy` and cheap (a single `SEXP`); covariant in `'a`.
25#[derive(Clone, Copy, Debug)]
26pub struct StrVec<'a>(SEXP, PhantomData<&'a str>);
27
28impl<'a> StrVec<'a> {
29    /// Wrap an existing `STRSXP` without additional checks.
30    ///
31    /// # Safety
32    ///
33    /// Caller must ensure `sexp` is a valid character vector (`STRSXP`)
34    /// that stays GC-reachable for the inferred lifetime `'a`.
35    #[inline]
36    pub const unsafe fn from_raw(sexp: SEXP) -> Self {
37        StrVec(sexp, PhantomData)
38    }
39
40    /// Get the underlying `SEXP`.
41    #[inline]
42    pub const fn as_sexp(self) -> SEXP {
43        self.0
44    }
45
46    /// Length of the character vector (number of elements).
47    #[inline]
48    pub fn len(self) -> isize {
49        self.0.xlength()
50    }
51
52    /// Returns true if the vector is empty.
53    #[inline]
54    pub fn is_empty(self) -> bool {
55        self.len() == 0
56    }
57
58    /// Get the CHARSXP at the given index.
59    ///
60    /// Returns `None` if out of bounds.
61    #[inline]
62    pub fn get_charsxp(self, idx: isize) -> Option<SEXP> {
63        if idx < 0 || idx >= self.len() {
64            return None;
65        }
66        Some(self.0.string_elt(idx))
67    }
68
69    /// Get the string at the given index (zero-copy), leashed to `'a`.
70    ///
71    /// Returns `None` if out of bounds or if the element is `NA_character_`.
72    /// Panics if the CHARSXP is not valid UTF-8 (should not happen in a UTF-8 locale).
73    #[inline]
74    pub fn get_str(self, idx: isize) -> Option<&'a str> {
75        let charsxp = self.get_charsxp(idx)?;
76        unsafe {
77            if charsxp == SEXP::na_string() {
78                return None;
79            }
80            // charsxp_to_str fabricates &'static; covariance narrows it to &'a.
81            Some(charsxp_to_str(charsxp))
82        }
83    }
84
85    /// Get the string at the given index as `Cow<str>` (encoding-safe), leashed to `'a`.
86    ///
87    /// Returns `Cow::Borrowed` for UTF-8 strings (zero-copy), `Cow::Owned` for
88    /// non-UTF-8 strings (translated via `Rf_translateCharUTF8`).
89    /// Returns `None` if out of bounds or `NA_character_`.
90    #[inline]
91    pub fn get_cow(self, idx: isize) -> Option<Cow<'a, str>> {
92        let charsxp = self.get_charsxp(idx)?;
93        unsafe {
94            if charsxp == SEXP::na_string() {
95                return None;
96            }
97            Some(charsxp_to_cow(charsxp))
98        }
99    }
100
101    /// Get the string at the given index as `&'static str` — the courageous escape.
102    ///
103    /// Unlike [`get_str`](Self::get_str), the returned reference is **not** leashed
104    /// to `'a`, so it can be stored in an `ExternalPtr`, a global, or sent across
105    /// threads. The string data lives in R's CHARSXP; it is only valid while that
106    /// `STRSXP` stays GC-reachable.
107    ///
108    /// # Safety
109    ///
110    /// The caller takes on the rooting obligation: the underlying `STRSXP` must be
111    /// kept reachable by R (e.g. via the `prot` slot of the `ExternalPtr` it is
112    /// stored in, or `R_PreserveObject`) for as long as the returned `&'static str`
113    /// is used. Letting R GC the source is use-after-free.
114    #[inline]
115    pub unsafe fn get_str_static(self, idx: isize) -> Option<&'static str> {
116        let charsxp = self.get_charsxp(idx)?;
117        unsafe {
118            if charsxp == SEXP::na_string() {
119                return None;
120            }
121            Some(charsxp_to_str(charsxp))
122        }
123    }
124
125    /// Get the string at the given index as `Cow<'static, str>` — the courageous escape.
126    ///
127    /// `Cow` analogue of [`get_str_static`](Self::get_str_static); same rooting
128    /// obligation for the borrowed (`Cow::Borrowed`) case.
129    ///
130    /// # Safety
131    ///
132    /// See [`get_str_static`](Self::get_str_static).
133    #[inline]
134    pub unsafe fn get_cow_static(self, idx: isize) -> Option<Cow<'static, str>> {
135        let charsxp = self.get_charsxp(idx)?;
136        unsafe {
137            if charsxp == SEXP::na_string() {
138                return None;
139            }
140            Some(charsxp_to_cow(charsxp))
141        }
142    }
143
144    /// Iterate over elements as `Option<&str>` (leashed to `'a`).
145    ///
146    /// `NA_character_` elements yield `None`, valid strings yield `Some(&str)`.
147    /// Zero-copy — each `&str` borrows directly from R's CHARSXP.
148    #[inline]
149    pub fn iter(self) -> StrVecIter<'a> {
150        StrVecIter {
151            vec: self,
152            idx: 0,
153            len: self.len(),
154        }
155    }
156
157    /// Iterate over elements as `Option<Cow<str>>` (encoding-safe, leashed to `'a`).
158    ///
159    /// Like [`iter`](Self::iter) but handles non-UTF-8 CHARSXPs gracefully.
160    #[inline]
161    pub fn iter_cow(self) -> StrVecCowIter<'a> {
162        StrVecCowIter {
163            vec: self,
164            idx: 0,
165            len: self.len(),
166        }
167    }
168
169    // region: Safe element insertion
170
171    /// Set a CHARSXP at the given index, protecting it during insertion.
172    ///
173    /// This is the safe way to insert a freshly allocated CHARSXP into a string vector.
174    ///
175    /// # Safety
176    ///
177    /// - Must be called from the R main thread
178    /// - `charsxp` must be a valid CHARSXP (from `Rf_mkChar*` or `STRING_ELT`)
179    /// - `self` must be a valid, protected STRSXP
180    ///
181    /// # Panics
182    ///
183    /// Panics if `idx` is out of bounds.
184    #[inline]
185    pub unsafe fn set_charsxp(self, idx: isize, charsxp: SEXP) {
186        assert!(idx >= 0 && idx < self.len(), "index out of bounds");
187        // SAFETY: caller guarantees R main thread and valid SEXPs
188        unsafe {
189            // Protect CHARSXP during SET_STRING_ELT.
190            // Note: Rf_mkCharLenCE returns a CHARSXP that may be from the global
191            // CHARSXP cache, but protection is still needed for newly allocated ones.
192            let _guard = OwnedProtect::new(charsxp);
193            self.0.set_string_elt(idx, charsxp);
194        }
195    }
196
197    /// Set a CHARSXP without protecting it.
198    ///
199    /// # Safety
200    ///
201    /// In addition to the safety requirements of [`set_charsxp`](Self::set_charsxp):
202    /// - The caller must ensure `charsxp` is already protected or from the
203    ///   global CHARSXP cache.
204    #[inline]
205    pub unsafe fn set_charsxp_unchecked(self, idx: isize, charsxp: SEXP) {
206        debug_assert!(idx >= 0 && idx < self.len(), "index out of bounds");
207        // SAFETY: caller guarantees charsxp is protected/cached
208        self.0.set_string_elt(idx, charsxp);
209    }
210
211    /// Set an element from a Rust string.
212    ///
213    /// Creates a CHARSXP from the string and inserts it safely.
214    ///
215    /// # Safety
216    ///
217    /// - Must be called from the R main thread
218    /// - `self` must be a valid, protected STRSXP
219    ///
220    /// # Panics
221    ///
222    /// Panics if `idx` is out of bounds.
223    #[inline]
224    pub unsafe fn set_str(self, idx: isize, s: &str) {
225        assert!(idx >= 0 && idx < self.len(), "index out of bounds");
226        // SAFETY: caller guarantees R main thread
227        unsafe {
228            let charsxp = SEXP::charsxp(s);
229            // CHARSXP may be cached, but protect anyway for safety
230            let _guard = OwnedProtect::new(charsxp);
231            self.0.set_string_elt(idx, charsxp);
232        }
233    }
234
235    /// Set an element to `NA_character_`.
236    ///
237    /// # Safety
238    ///
239    /// - Must be called from the R main thread
240    /// - `self` must be a valid, protected STRSXP
241    ///
242    /// # Panics
243    ///
244    /// Panics if `idx` is out of bounds.
245    #[inline]
246    pub unsafe fn set_na(self, idx: isize) {
247        assert!(idx >= 0 && idx < self.len(), "index out of bounds");
248        // R_NaString is a global constant, no protection needed
249        self.0.set_string_elt(idx, SEXP::na_string());
250    }
251
252    /// Set an element from an optional string.
253    ///
254    /// `None` becomes `NA_character_`.
255    ///
256    /// # Safety
257    ///
258    /// - Must be called from the R main thread
259    /// - `self` must be a valid, protected STRSXP
260    ///
261    /// # Panics
262    ///
263    /// Panics if `idx` is out of bounds.
264    #[inline]
265    pub unsafe fn set_opt_str(self, idx: isize, s: Option<&str>) {
266        match s {
267            Some(s) => unsafe { self.set_str(idx, s) },
268            None => unsafe { self.set_na(idx) },
269        }
270    }
271    // endregion
272}
273
274// region: StrVec iterators
275
276/// Iterator over `StrVec` elements as `Option<&str>` (leashed to `'a`).
277///
278/// Yields `None` for `NA_character_`, `Some(&str)` for valid strings.
279/// Zero-copy — each `&str` borrows directly from R's CHARSXP.
280pub struct StrVecIter<'a> {
281    vec: StrVec<'a>,
282    idx: isize,
283    len: isize,
284}
285
286impl<'a> Iterator for StrVecIter<'a> {
287    type Item = Option<&'a str>;
288
289    #[inline]
290    fn next(&mut self) -> Option<Self::Item> {
291        if self.idx >= self.len {
292            return None;
293        }
294        let charsxp = self.vec.0.string_elt(self.idx);
295        self.idx += 1;
296        if charsxp == SEXP::na_string() {
297            Some(None)
298        } else {
299            Some(Some(unsafe { charsxp_to_str(charsxp) }))
300        }
301    }
302
303    #[inline]
304    fn size_hint(&self) -> (usize, Option<usize>) {
305        let remaining = (self.len - self.idx) as usize;
306        (remaining, Some(remaining))
307    }
308}
309
310impl ExactSizeIterator for StrVecIter<'_> {}
311
312/// Iterator over `StrVec` elements as `Option<Cow<'a, str>>`.
313///
314/// Like [`StrVecIter`] but handles non-UTF-8 CHARSXPs via `Rf_translateCharUTF8`.
315pub struct StrVecCowIter<'a> {
316    vec: StrVec<'a>,
317    idx: isize,
318    len: isize,
319}
320
321impl<'a> Iterator for StrVecCowIter<'a> {
322    type Item = Option<Cow<'a, str>>;
323
324    #[inline]
325    fn next(&mut self) -> Option<Self::Item> {
326        if self.idx >= self.len {
327            return None;
328        }
329        let charsxp = self.vec.0.string_elt(self.idx);
330        self.idx += 1;
331        if charsxp == SEXP::na_string() {
332            Some(None)
333        } else {
334            Some(Some(unsafe { charsxp_to_cow(charsxp) }))
335        }
336    }
337
338    #[inline]
339    fn size_hint(&self) -> (usize, Option<usize>) {
340        let remaining = (self.len - self.idx) as usize;
341        (remaining, Some(remaining))
342    }
343}
344
345impl ExactSizeIterator for StrVecCowIter<'_> {}
346
347impl<'a> IntoIterator for StrVec<'a> {
348    type Item = Option<&'a str>;
349    type IntoIter = StrVecIter<'a>;
350
351    #[inline]
352    fn into_iter(self) -> Self::IntoIter {
353        self.iter()
354    }
355}
356
357// endregion
358
359// region: StrVecBuilder - efficient batch string vector construction
360
361/// Builder for constructing string vectors with efficient protection management.
362///
363/// # Example
364///
365/// ```ignore
366/// unsafe fn build_strvec(strings: &[&str]) -> SEXP {
367///     let scope = ProtectScope::new();
368///     let builder = StrVecBuilder::new(&scope, strings.len() as isize);
369///
370///     for (i, s) in strings.iter().enumerate() {
371///         builder.set_str(i as isize, s);
372///     }
373///
374///     builder.into_sexp()
375/// }
376/// ```
377pub struct StrVecBuilder<'a> {
378    vec: SEXP,
379    _scope: &'a ProtectScope,
380}
381
382impl<'a> StrVecBuilder<'a> {
383    /// Create a new string vector builder with the given length.
384    ///
385    /// # Safety
386    ///
387    /// Must be called from the R main thread.
388    #[inline]
389    pub unsafe fn new(scope: &'a ProtectScope, len: usize) -> Self {
390        // SAFETY: caller guarantees R main thread
391        let vec = unsafe { scope.alloc_character(len).into_raw() };
392        Self { vec, _scope: scope }
393    }
394
395    /// Create a new string vector builder via the **unchecked** FFI allocation path.
396    ///
397    /// `_unchecked` twin of [`new`](Self::new): the STRSXP is allocated with
398    /// `Rf_allocVector_unchecked` (see [`ProtectScope::alloc_character_unchecked`]),
399    /// bypassing the main-thread assertion. Use inside ALTREP callbacks,
400    /// `with_r_unwind_protect`, or `with_r_thread` bodies, and pair element
401    /// insertion with the `_unchecked` string-element setters.
402    ///
403    /// # Safety
404    ///
405    /// Must be called from the R main thread, in a context where the checked-FFI
406    /// assertion is intentionally bypassed (see CLAUDE.md "FFI thread checking").
407    #[inline]
408    pub unsafe fn new_unchecked(scope: &'a ProtectScope, len: usize) -> Self {
409        // SAFETY: caller guarantees R main thread in a checked-bypass context.
410        let vec = unsafe { scope.alloc_character_unchecked(len).into_raw() };
411        Self { vec, _scope: scope }
412    }
413
414    /// Set an element from a Rust string.
415    ///
416    /// # Safety
417    ///
418    /// Must be called from the R main thread.
419    #[inline]
420    pub unsafe fn set_str(&self, idx: isize, s: &str) {
421        debug_assert!(idx >= 0 && idx < self.vec.xlength());
422        let charsxp = SEXP::charsxp(s);
423        self.vec.set_string_elt(idx, charsxp);
424    }
425
426    /// Set an element to `NA_character_`.
427    ///
428    /// # Safety
429    ///
430    /// Must be called from the R main thread.
431    #[inline]
432    pub unsafe fn set_na(&self, idx: isize) {
433        debug_assert!(idx >= 0 && idx < self.vec.xlength());
434        self.vec.set_string_elt(idx, SEXP::na_string());
435    }
436
437    /// Set an element from an optional string.
438    ///
439    /// # Safety
440    ///
441    /// Must be called from the R main thread.
442    #[inline]
443    pub unsafe fn set_opt_str(&self, idx: isize, s: Option<&str>) {
444        match s {
445            // SAFETY: caller guarantees R main thread
446            Some(s) => unsafe { self.set_str(idx, s) },
447            None => unsafe { self.set_na(idx) },
448        }
449    }
450
451    /// Get the underlying SEXP.
452    #[inline]
453    pub fn as_sexp(&self) -> SEXP {
454        self.vec
455    }
456
457    /// Convert to a `StrVec` view, leashed to the builder's protect scope `'a`.
458    #[inline]
459    pub fn into_strvec(self) -> StrVec<'a> {
460        StrVec(self.vec, PhantomData)
461    }
462
463    /// Convert to the underlying SEXP.
464    #[inline]
465    pub fn into_sexp(self) -> SEXP {
466        self.vec
467    }
468
469    /// Get the length.
470    #[inline]
471    pub fn len(&self) -> isize {
472        self.vec.xlength()
473    }
474
475    /// Check if empty.
476    #[inline]
477    pub fn is_empty(&self) -> bool {
478        self.len() == 0
479    }
480}
481// endregion
482
483// region: Trait implementations
484
485impl IntoR for StrVec<'_> {
486    type Error = std::convert::Infallible;
487    fn try_into_sexp(self) -> Result<SEXP, Self::Error> {
488        Ok(self.into_sexp())
489    }
490    unsafe fn try_into_sexp_unchecked(self) -> Result<SEXP, Self::Error> {
491        self.try_into_sexp()
492    }
493    #[inline]
494    fn into_sexp(self) -> SEXP {
495        self.0
496    }
497}
498
499impl<'a> TryFromSexp for StrVec<'a> {
500    type Error = SexpError;
501
502    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
503        let actual = sexp.type_of();
504        if actual != STRSXP {
505            return Err(SexpTypeError {
506                expected: STRSXP,
507                actual,
508            }
509            .into());
510        }
511        Ok(StrVec(sexp, PhantomData))
512    }
513}
514// endregion
515
516// region: ProtectedStrVec — GC-protected string vector with proper lifetimes
517
518/// GC-protected view over an R character vector (`STRSXP`).
519///
520/// Unlike [`StrVec`] (which is `Copy` and trusts the caller for GC protection),
521/// `ProtectedStrVec` wraps a [`Protected<'static, StrVec<'static>>`](crate::gc_protect::Protected)
522/// that keeps the STRSXP alive — the `'static` on the inner view is sound here
523/// because the protect guard *is* the rooting obligation. All borrowed data
524/// (`&str`, iterators) it hands out has its lifetime tied to `&self`, not
525/// `'static` — preventing use-after-GC bugs at compile time.
526///
527/// # When to use
528///
529/// - **`StrVec`**: for SEXP arguments to `.Call` (R protects them), or when you
530///   manage protection yourself. Lightweight, `Copy`.
531/// - **`ProtectedStrVec`**: when you allocate or receive an STRSXP and need to
532///   keep it alive beyond the immediate scope. Not `Copy`.
533///
534/// # Example
535///
536/// ```ignore
537/// #[miniextendr]
538/// pub fn count_unique(strings: ProtectedStrVec) -> i32 {
539///     let unique: HashSet<&str> = strings.iter()
540///         .filter_map(|s| s)
541///         .collect();
542///     unique.len() as i32
543/// }
544/// ```
545pub struct ProtectedStrVec {
546    protected: Protected<'static, StrVec<'static>>,
547    len: isize,
548}
549
550impl ProtectedStrVec {
551    /// Create a protected view over an STRSXP.
552    ///
553    /// Calls `Rf_protect` on the SEXP. Use [`from_sexp_trusted`](Self::from_sexp_trusted)
554    /// when the SEXP is already protected (e.g., `.Call` arguments) to avoid
555    /// double-protecting.
556    ///
557    /// # Safety
558    ///
559    /// - `sexp` must be a valid STRSXP.
560    /// - Must be called from the R main thread.
561    #[inline]
562    pub unsafe fn new(sexp: SEXP) -> Self {
563        let inner = unsafe { StrVec::from_raw(sexp) };
564        let len = inner.len();
565        Self {
566            protected: unsafe { Protected::new(sexp, inner) },
567            len,
568        }
569    }
570
571    /// Create a view without adding GC protection.
572    ///
573    /// Use this when the SEXP is already protected by R (e.g., a `.Call`
574    /// argument, or in a `ProtectScope`). Avoids the redundant
575    /// `Rf_protect`/`Rf_unprotect` pair.
576    ///
577    /// The lifetime-bound `&str` borrows are still enforced — this only
578    /// skips the protect stack push, not the safety guarantees.
579    ///
580    /// # Safety
581    ///
582    /// - `sexp` must be a valid STRSXP.
583    /// - `sexp` must remain GC-protected for the lifetime of this struct.
584    /// - Must be called from the R main thread.
585    #[inline]
586    pub unsafe fn from_sexp_trusted(sexp: SEXP) -> Self {
587        let inner = unsafe { StrVec::from_raw(sexp) };
588        let len = inner.len();
589        Self {
590            protected: unsafe { Protected::from_trusted(sexp, inner) },
591            len,
592        }
593    }
594
595    /// Number of elements.
596    #[inline]
597    pub fn len(&self) -> isize {
598        self.len
599    }
600
601    /// Whether the vector is empty.
602    #[inline]
603    pub fn is_empty(&self) -> bool {
604        self.len == 0
605    }
606
607    /// Get the string at index (zero-copy, lifetime tied to `&self`).
608    ///
609    /// Returns `None` for out-of-bounds or `NA_character_`.
610    #[inline]
611    pub fn get_str(&self, idx: isize) -> Option<&str> {
612        // charsxp_to_str returns &'static str, but lifetime elision
613        // restricts it to &'_ (tied to &self) — correct: data lives
614        // as long as the Protected guard keeps the STRSXP alive.
615        self.protected.get().get_str(idx)
616    }
617
618    /// Get the string at index as `Cow<str>` (encoding-safe, lifetime tied to `&self`).
619    #[inline]
620    pub fn get_cow(&self, idx: isize) -> Option<Cow<'_, str>> {
621        self.protected.get().get_cow(idx)
622    }
623
624    /// Iterate over elements as `Option<&str>` (lifetime tied to `&self`).
625    #[inline]
626    pub fn iter(&self) -> ProtectedStrVecIter<'_> {
627        ProtectedStrVecIter {
628            vec: self,
629            idx: 0,
630            len: self.len,
631        }
632    }
633
634    /// Iterate over elements as `Option<Cow<str>>` (encoding-safe).
635    #[inline]
636    pub fn iter_cow(&self) -> ProtectedStrVecCowIter<'_> {
637        ProtectedStrVecCowIter {
638            vec: self,
639            idx: 0,
640            len: self.len,
641        }
642    }
643
644    /// Get the underlying SEXP (still protected by this handle).
645    #[inline]
646    pub fn as_sexp(&self) -> SEXP {
647        self.protected.get().as_sexp()
648    }
649
650    /// Get the inner `StrVec` view, leashed to `&self` (the protect guard).
651    #[inline]
652    pub fn as_strvec(&self) -> StrVec<'_> {
653        *self.protected.get()
654    }
655}
656
657/// Iterator over `ProtectedStrVec` with lifetime tied to the protection guard.
658pub struct ProtectedStrVecIter<'a> {
659    vec: &'a ProtectedStrVec,
660    idx: isize,
661    len: isize,
662}
663
664impl<'a> Iterator for ProtectedStrVecIter<'a> {
665    type Item = Option<&'a str>;
666
667    #[inline]
668    fn next(&mut self) -> Option<Self::Item> {
669        if self.idx >= self.len {
670            return None;
671        }
672        let result = self.vec.get_str(self.idx);
673        self.idx += 1;
674        // get_str returns None for NA; we need to distinguish "end of iter" from "NA element"
675        // Wrap: Some(None) = NA, Some(Some(&str)) = value, None = end
676        Some(result)
677    }
678
679    #[inline]
680    fn size_hint(&self) -> (usize, Option<usize>) {
681        let remaining = (self.len - self.idx) as usize;
682        (remaining, Some(remaining))
683    }
684}
685
686impl ExactSizeIterator for ProtectedStrVecIter<'_> {}
687
688/// Encoding-safe iterator over `ProtectedStrVec`.
689pub struct ProtectedStrVecCowIter<'a> {
690    vec: &'a ProtectedStrVec,
691    idx: isize,
692    len: isize,
693}
694
695impl<'a> Iterator for ProtectedStrVecCowIter<'a> {
696    type Item = Option<Cow<'a, str>>;
697
698    #[inline]
699    fn next(&mut self) -> Option<Self::Item> {
700        if self.idx >= self.len {
701            return None;
702        }
703        let result = self.vec.get_cow(self.idx);
704        self.idx += 1;
705        Some(result)
706    }
707
708    #[inline]
709    fn size_hint(&self) -> (usize, Option<usize>) {
710        let remaining = (self.len - self.idx) as usize;
711        (remaining, Some(remaining))
712    }
713}
714
715impl ExactSizeIterator for ProtectedStrVecCowIter<'_> {}
716
717impl<'a> IntoIterator for &'a ProtectedStrVec {
718    type Item = Option<&'a str>;
719    type IntoIter = ProtectedStrVecIter<'a>;
720
721    #[inline]
722    fn into_iter(self) -> Self::IntoIter {
723        self.iter()
724    }
725}
726
727impl IntoR for ProtectedStrVec {
728    type Error = std::convert::Infallible;
729    fn try_into_sexp(self) -> Result<SEXP, Self::Error> {
730        Ok(self.as_sexp())
731    }
732    unsafe fn try_into_sexp_unchecked(self) -> Result<SEXP, Self::Error> {
733        Ok(self.as_sexp())
734    }
735    #[inline]
736    fn into_sexp(self) -> SEXP {
737        self.as_sexp()
738    }
739}
740
741impl TryFromSexp for ProtectedStrVec {
742    type Error = SexpError;
743
744    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
745        let actual = sexp.type_of();
746        if actual != STRSXP {
747            return Err(SexpTypeError {
748                expected: STRSXP,
749                actual,
750            }
751            .into());
752        }
753        // Use from_sexp_trusted: TryFromSexp is called from generated .Call
754        // wrappers where R already protects the argument. No need to double-protect.
755        Ok(unsafe { ProtectedStrVec::from_sexp_trusted(sexp) })
756    }
757}
758
759impl std::fmt::Debug for ProtectedStrVec {
760    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
761        f.debug_struct("ProtectedStrVec")
762            .field("len", &self.len)
763            .finish()
764    }
765}
766// endregion