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