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