Skip to main content

miniextendr_api/
gc_protect.rs

1//! GC protection tools built on R's PROTECT stack.
2//!
3//! This module provides RAII wrappers around R's GC protection primitives.
4//!
5//! # Submodules
6//!
7//! | Module | Contents |
8//! |--------|----------|
9//! | [`tls`] | Thread-local convenience API — `tls::protect(x)` without passing `&ProtectScope` |
10//!
11//! # Core Types
12//!
13//! - [`ProtectScope`] — RAII scope that calls `UNPROTECT(n)` on drop
14//! - [`OwnedProtect`] — single-value RAII protect/unprotect
15//! - [`Root`] — lifetime-tied handle to a protected SEXP
16//! - [`ReprotectSlot`] — `PROTECT_WITH_INDEX` + `REPROTECT` for mutable slots
17//! - [`Protector`] — the trait over `ProtectScope` and
18//!   [`ProtectPool`](crate::protect_pool::ProtectPool)
19//!
20//! # Protection Strategies in miniextendr
21//!
22//! miniextendr provides three complementary protection mechanisms for different scenarios:
23//!
24//! | Strategy | Module | Lifetime | Release Order | Use Case |
25//! |----------|--------|----------|---------------|----------|
26//! | **PROTECT stack** | [`gc_protect`](crate::gc_protect) | Within `.Call` | LIFO (stack) | Temporary allocations |
27//! | **VECSXP pool** | [`protect_pool`](crate::protect_pool) | Until released or the pool drops | Any order | Values a struct owns; many values; cross-call roots |
28//! | **R ownership** | [`ExternalPtr`](struct@crate::ExternalPtr) | Until R GCs | R decides | Rust data owned by R |
29//!
30//! ## When to Use Each
31//!
32//! **Use `gc_protect` (this module) when:**
33//! - You allocate R objects during a `.Call` and need them protected until return
34//! - You want RAII-based automatic balancing of PROTECT/UNPROTECT
35//! - Protection is short-lived (within a single function)
36//!
37//! **Use [`ProtectPool`](crate::protect_pool::ProtectPool) when:**
38//! - A struct owns the values, or they may be dropped in any order relative to
39//!   other roots: the per-call root set, `pool.root(value)`
40//! - There are many values: no protect-stack entry per value
41//! - Objects must survive across multiple `.Call` invocations, released one at
42//!   a time in any order (O(1) insert/release with generational keys)
43//!
44//! **Use [`ExternalPtr`](struct@crate::ExternalPtr) when:**
45//! - You want R to own a Rust value
46//! - The Rust value should be dropped when R garbage collects the pointer
47//! - You're exposing Rust structs to R code
48//!
49//! ## Visual Overview
50//!
51//! ```text
52//! ┌─────────────────────────────────────────────────────────────────┐
53//! │  .Call("my_func", x)                                            │
54//! │  ┌──────────────────────────────────────────────────────────┐   │
55//! │  │  ProtectScope::new()                                     │   │
56//! │  │  ├── protect(Rf_allocVector(...))  // temp allocation    │   │
57//! │  │  ├── protect(Rf_allocVector(...))  // another temp       │   │
58//! │  │  └── UNPROTECT(n) on scope drop                          │   │
59//! │  └──────────────────────────────────────────────────────────┘   │
60//! │                          ↓ return SEXP                          │
61//! └─────────────────────────────────────────────────────────────────┘
62//!
63//! ┌─────────────────────────────────────────────────────────────────┐
64//! │  ProtectPool (a root set; any drop order)                       │
65//! │  ├── pool.root(value)         // keep until the pool drops      │
66//! │  ├── pool.insert(sexp)        // O(1), generational key         │
67//! │  ├── ... allocations, .Calls ... // objects stay rooted         │
68//! │  ├── pool.release(key)        // O(1) remove                    │
69//! │  └── drop(pool)               // releases everything left       │
70//! └─────────────────────────────────────────────────────────────────┘
71//!
72//! ┌─────────────────────────────────────────────────────────────────┐
73//! │  ExternalPtr<MyStruct> (R owns Rust data)                       │
74//! │  ├── Construction: temporary Rf_protect                         │
75//! │  ├── Return to R → R owns the EXTPTRSXP                         │
76//! │  └── R GC → finalizer runs → Rust Drop executes                 │
77//! └─────────────────────────────────────────────────────────────────┘
78//! ```
79//!
80//! # Types in This Module
81//!
82//! This module provides RAII wrappers around R's GC protection primitives:
83//!
84//! | Type | Purpose |
85//! |------|---------|
86//! | [`ProtectScope`] | Batch protection with automatic `UNPROTECT(n)` on drop |
87//! | [`Root<'scope>`] | Lightweight handle tied to a scope's lifetime |
88//! | [`OwnedProtect`] | Single-value RAII guard for simple cases |
89//! | [`ReprotectSlot<'scope>`] | Protected slot supporting replace-under-protection |
90//!
91//! # Design Principles
92//!
93//! - `ProtectScope` owns the responsibility of calling `UNPROTECT(n)`
94//! - `Root<'a>` is a move-friendly, non-dropping handle whose lifetime ties to the scope
95//! - `ReprotectSlot<'a>` supports replace-under-protection via `PROTECT_WITH_INDEX`/`REPROTECT`
96//!
97//! # Safety Model
98//!
99//! These tools are `unsafe` to create because they require:
100//!
101//! 1. **Running on the R main thread** - R's API is not thread-safe
102//! 2. **No panics across FFI** - Rust panics must not unwind across C boundary
103//! 3. **Understanding R errors** - If R raises an error (`longjmp`), Rust destructors
104//!    will not run, so scope-based unprotection will leak
105//!
106//! For cleanup that survives R errors, use `R_UnwindProtect` boundaries in your
107//! `.Call` trampoline (see [`unwind_protect`](crate::unwind_protect)).
108//!
109//! # Example
110//!
111//! ```ignore
112//! use miniextendr_api::gc_protect::ProtectScope;
113//! use miniextendr_api::SEXP;
114//!
115//! unsafe fn process_vectors(x: SEXP, y: SEXP) -> SEXP {
116//!     let scope = ProtectScope::new();
117//!
118//!     // Protect multiple values
119//!     let x = scope.protect(x);
120//!     let y = scope.protect(y);
121//!
122//!     // Work with protected values...
123//!     let result = scope.protect(some_r_function(x.get(), y.get()));
124//!
125//!     result.into_raw()
126//! } // UNPROTECT(3) called automatically
127//! ```
128//!
129//! # Container Insertion Patterns
130//!
131//! When building containers (lists, character vectors), children need protection
132//! between allocation and insertion:
133//!
134//! ```ignore
135//! // WRONG - child unprotected between allocation and SET_VECTOR_ELT
136//! let child = Rf_allocVector(REALSXP, 10);  // unprotected!
137//! list.set_vector_elt(0, child);           // GC could occur before this!
138//!
139//! // CORRECT - use safe insertion methods
140//! let list = List::from_raw(scope.alloc_vecsxp(n).into_raw());
141//! for i in 0..n {
142//!     let child = Rf_allocVector(REALSXP, 10);
143//!     list.set_elt(i, child);  // protects child during insertion
144//! }
145//!
146//! // EFFICIENT - use ListBuilder with scope
147//! let builder = ListBuilder::new(&scope, n);
148//! for i in 0..n {
149//!     let child = scope.alloc_real(10).into_raw();
150//!     builder.set(i, child);  // child already protected by scope
151//! }
152//! ```
153//!
154//! See [`List::set_elt`](crate::list::List::set_elt),
155//! [`ListBuilder`](crate::list::ListBuilder), and
156//! [`StrVec::set_str`](crate::strvec::StrVec::set_str) for safe container APIs.
157//!
158//! # Reassignment with `ReprotectSlot`
159//!
160//! Use [`ReprotectSlot`] when you need to reassign a protected value multiple times
161//! without growing the protection stack:
162//!
163//! ```ignore
164//! let slot = scope.protect_with_index(initial_value);
165//! for item in items {
166//!     let new_value = process(slot.get(), item);
167//!     slot.set(new_value);  // R_Reprotect, stack count unchanged
168//! }
169//! ```
170//!
171//! This avoids the LIFO drop-order pitfall of reassigning `OwnedProtect` guards.
172
173use crate::sexp_types::cetype_t;
174use crate::sys::{
175    R_MakeExternalPtr, R_NewEnv, R_ProtectWithIndex, R_Reprotect, Rf_alloc3DArray, Rf_allocArray,
176    Rf_allocList, Rf_allocMatrix, Rf_allocS4Object, Rf_allocVector, Rf_allocVector_unchecked,
177    Rf_cons, Rf_lcons, Rf_lengthgets, Rf_mkCharLenCE, Rf_protect, Rf_unprotect, Rf_xlengthgets,
178};
179use crate::{R_xlen_t, RNativeType, SEXP, SEXPTYPE, SexpExt};
180use core::cell::{Cell, RefCell};
181use core::marker::PhantomData;
182use std::rc::Rc;
183
184/// R's PROTECT_INDEX type (just `c_int` under the hood).
185pub type ProtectIndex = ::std::os::raw::c_int;
186
187/// Enforces `!Send + !Sync` (R API is not thread-safe).
188type NoSendSync = PhantomData<Rc<()>>;
189
190// region: Protector trait
191
192/// A scope-like GC protection backend: everything it protects stays rooted
193/// until the protector drops.
194///
195/// Code that allocates several intermediate SEXPs can take `&mut impl Protector`
196/// and leave the choice of backend to its caller. There is no individual
197/// release through this trait; for that, use
198/// [`ProtectPool::insert`](crate::protect_pool::ProtectPool::insert) and
199/// [`ProtectPool::release`](crate::protect_pool::ProtectPool::release).
200///
201/// # Which backend
202///
203/// - A temporary within one call: [`ProtectScope`], R's protect stack. The
204///   cheapest; its values unprotect together, in LIFO order with the rest of
205///   the stack.
206/// - Many values, any drop order, or owned by a struct:
207///   [`ProtectPool`](crate::protect_pool::ProtectPool), one R list that the
208///   pool preserves. No protect-stack entry per value, and dropping it
209///   releases everything it holds whatever the order.
210/// - One long-lived object: [`Preserved`](crate::Preserved), a handle, not a
211///   `Protector`.
212///
213/// There is no precious-list `Protector`. `R_PreserveObject` per value allocates
214/// a cell each time, and `R_ReleaseObject` scans the precious list from its
215/// head, so releasing many values (above all in insertion order) is O(n²), the
216/// pattern the pool exists to avoid. [`OwnedProtect`] is a single
217/// protect-stack entry, not a precious-list root.
218///
219/// # Safety
220///
221/// Implementations must keep every SEXP passed to
222/// [`protect`](Self::protect) reachable by R's collector for as long as the
223/// protector lives: safe code relies on that to hold the returned SEXP across
224/// allocations.
225pub unsafe trait Protector {
226    /// Root `sexp` until the protector drops, and return it.
227    ///
228    /// `sexp` may be a fresh, unprotected object. Call it on R's main thread,
229    /// where the protector was made (both implementations are `!Send`).
230    fn protect(&mut self, sexp: SEXP) -> SEXP;
231}
232
233// SAFETY: `ProtectScope::new` is `unsafe`: its caller promised to use and drop
234// the scope on R's main thread and to keep R's protect stack LIFO around it (no
235// protection through the scope while a younger scope or guard is alive). Under
236// that promise, protecting through `&mut ProtectScope` pushes one entry that
237// the scope's `UNPROTECT(n)` pops at drop, so the value stays rooted for the
238// scope's whole life. `Rf_protect` keeps a fresh object reachable before
239// anything else allocates.
240unsafe impl Protector for ProtectScope {
241    #[inline]
242    fn protect(&mut self, sexp: SEXP) -> SEXP {
243        // SAFETY: see the impl's SAFETY comment.
244        unsafe { self.protect_raw(sexp) }
245    }
246}
247
248// SAFETY: `ProtectPool::insert` stores `sexp` in the pool's preserved list,
249// which roots it until its slot is released or replaced, and this impl keeps
250// no key to do either: the value stays rooted until the pool drops. An insert
251// that grows the pool keeps `sexp` on the protect stack across the allocation.
252unsafe impl Protector for crate::protect_pool::ProtectPool {
253    #[inline]
254    fn protect(&mut self, sexp: SEXP) -> SEXP {
255        self.root(sexp)
256    }
257}
258
259// endregion
260
261// region: ProtectScope
262
263/// A scope that automatically balances `UNPROTECT(n)` on drop.
264///
265/// This is the primary tool for managing GC protection in batch operations.
266/// Each call to [`protect`][Self::protect] or [`protect_with_index`][Self::protect_with_index]
267/// increments an internal counter; when the scope is dropped, `UNPROTECT(n)` is called.
268///
269/// # Example
270///
271/// ```ignore
272/// unsafe fn my_call(x: SEXP, y: SEXP) -> SEXP {
273///     let scope = ProtectScope::new();
274///     let x = scope.protect(x);
275///     let y = scope.protect(y);
276///
277///     // Both x and y are protected until scope drops
278///     let result = scope.protect(some_operation(x.get(), y.get()));
279///     result.get()
280/// } // UNPROTECT(3)
281/// ```
282///
283/// # Nested Scopes
284///
285/// Scopes can be nested. Each scope tracks only its own protections:
286///
287/// ```ignore
288/// unsafe fn outer(x: SEXP) -> SEXP {
289///     let scope = ProtectScope::new();
290///     let x = scope.protect(x);
291///
292///     let result = helper(&scope, x.get());
293///     scope.protect(result).get()
294/// } // UNPROTECT(2)
295///
296/// unsafe fn helper(_parent: &ProtectScope, x: SEXP) -> SEXP {
297///     let scope = ProtectScope::new();
298///     let temp = scope.protect(allocate_something());
299///     combine(x, temp.get())
300/// } // UNPROTECT(1) - only this scope's protections
301/// ```
302pub struct ProtectScope {
303    n: Cell<i32>,
304    armed: Cell<bool>,
305    // Created only by rooted child conversions. A pool avoids one PROTECT
306    // stack entry per list/string element, and supports later child additions.
307    children: ChildRoots,
308    _nosend: NoSendSync,
309}
310
311impl ProtectScope {
312    /// Create a new protection scope.
313    ///
314    /// # Safety
315    ///
316    /// - Use and drop the scope on R's main thread.
317    /// - Keep R's protect stack LIFO around it. The scope unprotects with
318    ///   `UNPROTECT(n)`, which pops the top `n` entries of the stack whoever
319    ///   pushed them. So don't protect through this scope while a scope or
320    ///   guard made after it is alive, and drop it after every such scope or
321    ///   guard. A scope moved into a struct is easy to drop out of this order;
322    ///   a [`ProtectPool`](crate::ProtectPool) owned by a struct has no such
323    ///   rule.
324    ///
325    /// The [`Protector`] impl relies on this promise to make protecting
326    /// through a constructed scope safe.
327    #[inline]
328    pub unsafe fn new() -> Self {
329        Self {
330            n: Cell::new(0),
331            armed: Cell::new(true),
332            children: ChildRoots::default(),
333            _nosend: PhantomData,
334        }
335    }
336
337    /// Protect `x` and return a rooted handle tied to this scope.
338    ///
339    /// This always calls `Rf_protect`. The protection is released when
340    /// the scope is dropped (along with all other protections in this scope).
341    ///
342    /// # Safety
343    ///
344    /// - Must be called from the R main thread
345    /// - `x` must be a valid SEXP
346    #[inline]
347    pub unsafe fn protect<'a>(&'a self, x: SEXP) -> Root<'a> {
348        let y = unsafe { Rf_protect(x) };
349        self.n.set(self.n.get() + 1);
350        Root {
351            sexp: y,
352            scope: self,
353        }
354    }
355
356    /// Protect and return the raw `SEXP` (sometimes more convenient).
357    ///
358    /// # Safety
359    ///
360    /// Same as [`protect`][Self::protect].
361    #[inline]
362    pub unsafe fn protect_raw(&self, x: SEXP) -> SEXP {
363        let y = unsafe { Rf_protect(x) };
364        self.n.set(self.n.get() + 1);
365        y
366    }
367
368    /// Protect `x` with an index slot so it can be replaced later via [`R_Reprotect`].
369    ///
370    /// Use this when you need to update a protected value in-place without
371    /// growing the protection stack.
372    ///
373    /// # Safety
374    ///
375    /// - Must be called from the R main thread
376    /// - `x` must be a valid SEXP
377    ///
378    /// # Example
379    ///
380    /// ```ignore
381    /// unsafe fn accumulate(values: &[SEXP]) -> SEXP {
382    ///     let scope = ProtectScope::new();
383    ///     let slot = scope.protect_with_index(values[0]);
384    ///
385    ///     for &v in &values[1..] {
386    ///         let combined = combine(slot.get(), v);
387    ///         slot.set(combined);  // Reprotect without growing stack
388    ///     }
389    ///
390    ///     slot.get()
391    /// }
392    /// ```
393    #[inline]
394    pub unsafe fn protect_with_index<'a>(&'a self, x: SEXP) -> ReprotectSlot<'a> {
395        let mut idx: ProtectIndex = 0;
396        unsafe { R_ProtectWithIndex(x, std::ptr::from_mut(&mut idx)) };
397        self.n.set(self.n.get() + 1);
398        ReprotectSlot {
399            idx,
400            cur: Cell::new(x),
401            _scope: PhantomData,
402            _nosend: PhantomData,
403        }
404    }
405
406    /// Protect two values at once (convenience method).
407    ///
408    /// # Safety
409    ///
410    /// Same as [`protect`][Self::protect].
411    #[inline]
412    pub unsafe fn protect2<'a>(&'a self, a: SEXP, b: SEXP) -> (Root<'a>, Root<'a>) {
413        // SAFETY: caller guarantees R main thread and valid SEXPs
414        unsafe { (self.protect(a), self.protect(b)) }
415    }
416
417    /// Protect three values at once (convenience method).
418    ///
419    /// # Safety
420    ///
421    /// Same as [`protect`][Self::protect].
422    #[inline]
423    pub unsafe fn protect3<'a>(
424        &'a self,
425        a: SEXP,
426        b: SEXP,
427        c: SEXP,
428    ) -> (Root<'a>, Root<'a>, Root<'a>) {
429        // SAFETY: caller guarantees R main thread and valid SEXPs
430        unsafe { (self.protect(a), self.protect(b), self.protect(c)) }
431    }
432
433    /// Return the current protection count.
434    #[inline]
435    pub fn count(&self) -> i32 {
436        self.n.get()
437    }
438
439    /// Escape hatch: disable `UNPROTECT` on drop.
440    ///
441    /// After calling this, the scope will **not** unprotect its values when dropped.
442    /// You become responsible for ensuring correct unprotection.
443    ///
444    /// # Safety
445    ///
446    /// You must ensure the protects performed in this scope are correctly
447    /// unprotected elsewhere, or you will leak protect stack entries.
448    #[inline]
449    pub unsafe fn disarm(&self) {
450        self.armed.set(false);
451    }
452
453    /// Re-arm a previously disarmed scope.
454    ///
455    /// # Safety
456    ///
457    /// Only call if you know the scope was disarmed and you want to restore
458    /// automatic unprotection. Be careful not to double-unprotect.
459    #[inline]
460    pub unsafe fn rearm(&self) {
461        self.armed.set(true);
462    }
463
464    // region: Allocation + Protection Helpers
465
466    /// Allocate a vector of the given type and length, and immediately protect it.
467    ///
468    /// This combines allocation and protection in a single step, eliminating the
469    /// GC gap that exists when you separately allocate and then protect.
470    ///
471    /// # Safety
472    ///
473    /// - Must be called from the R main thread
474    /// - Only protects the newly allocated object; does not protect other live
475    ///   unprotected objects during allocation
476    ///
477    /// # Example
478    ///
479    /// ```ignore
480    /// unsafe fn make_ints(n: R_xlen_t) -> SEXP {
481    ///     let scope = ProtectScope::new();
482    ///     let vec = scope.alloc_vector(SEXPTYPE::INTSXP, n);
483    ///     // fill via INTEGER(vec.get()) ...
484    ///     vec.get()
485    /// }
486    /// ```
487    #[inline]
488    pub unsafe fn alloc_vector<'a>(&'a self, ty: SEXPTYPE, n: R_xlen_t) -> Root<'a> {
489        // SAFETY: caller guarantees R main thread
490        let sexp = unsafe { Rf_allocVector(ty, n) };
491        unsafe { self.protect(sexp) }
492    }
493
494    /// Allocate a matrix of the given type and dimensions, and immediately protect it.
495    ///
496    /// # Safety
497    ///
498    /// Same as [`alloc_vector`][Self::alloc_vector].
499    #[inline]
500    pub unsafe fn alloc_matrix<'a>(&'a self, ty: SEXPTYPE, nrow: i32, ncol: i32) -> Root<'a> {
501        let sexp = unsafe { Rf_allocMatrix(ty, nrow, ncol) };
502        unsafe { self.protect(sexp) }
503    }
504
505    /// Allocate a list (VECSXP) of the given length and immediately protect it.
506    ///
507    /// # Safety
508    ///
509    /// Same as [`alloc_vector`][Self::alloc_vector].
510    #[inline]
511    pub unsafe fn alloc_list<'a>(&'a self, n: i32) -> Root<'a> {
512        let sexp = unsafe { Rf_allocList(n) };
513        unsafe { self.protect(sexp) }
514    }
515
516    /// Allocate a STRSXP (character vector) of the given length and immediately protect it.
517    ///
518    /// # Safety
519    ///
520    /// Same as [`alloc_vector`][Self::alloc_vector].
521    #[inline]
522    pub unsafe fn alloc_strsxp<'a>(&'a self, n: usize) -> Root<'a> {
523        unsafe { self.alloc_character(n) }
524    }
525
526    /// Allocate a VECSXP (generic list) of the given length and immediately protect it.
527    ///
528    /// # Safety
529    ///
530    /// Same as [`alloc_vector`][Self::alloc_vector].
531    #[inline]
532    pub unsafe fn alloc_vecsxp<'a>(&'a self, n: usize) -> Root<'a> {
533        let len = R_xlen_t::try_from(n).expect("length exceeds R_xlen_t");
534        unsafe { self.alloc_vector(SEXPTYPE::VECSXP, len) }
535    }
536
537    /// Allocate a vector via the **unchecked** FFI path and immediately protect it.
538    ///
539    /// `_unchecked` twin of [`alloc_vector`](Self::alloc_vector): allocates with
540    /// `Rf_allocVector_unchecked` (bypassing the main-thread assertion / worker
541    /// round-trip) for use inside ALTREP callbacks, `with_r_unwind_protect`, or
542    /// `with_r_thread` bodies. Protection still goes through the (checked)
543    /// `Rf_protect` — matching the established `OwnedProtect`-in-unchecked-context
544    /// idiom — and is released with the rest of the scope on drop.
545    ///
546    /// # Safety
547    ///
548    /// Must be called from the R main thread, and only from a context where the
549    /// checked-FFI assertion is intentionally bypassed (see CLAUDE.md "FFI thread
550    /// checking").
551    #[inline]
552    pub unsafe fn alloc_vector_unchecked<'a>(&'a self, ty: SEXPTYPE, n: R_xlen_t) -> Root<'a> {
553        // SAFETY: caller guarantees R main thread in a checked-bypass context.
554        let sexp = unsafe { Rf_allocVector_unchecked(ty, n) };
555        unsafe { self.protect(sexp) }
556    }
557
558    /// Allocate a VECSXP via the **unchecked** FFI path, protected.
559    ///
560    /// `_unchecked` twin of [`alloc_vecsxp`](Self::alloc_vecsxp). See
561    /// [`alloc_vector_unchecked`](Self::alloc_vector_unchecked) for the safety
562    /// contract.
563    ///
564    /// # Safety
565    ///
566    /// Same as [`alloc_vector_unchecked`](Self::alloc_vector_unchecked).
567    #[inline]
568    pub unsafe fn alloc_vecsxp_unchecked<'a>(&'a self, n: usize) -> Root<'a> {
569        let len = R_xlen_t::try_from(n).expect("length exceeds R_xlen_t");
570        unsafe { self.alloc_vector_unchecked(SEXPTYPE::VECSXP, len) }
571    }
572
573    /// Allocate a STRSXP via the **unchecked** FFI path, protected.
574    ///
575    /// `_unchecked` twin of [`alloc_character`](Self::alloc_character). See
576    /// [`alloc_vector_unchecked`](Self::alloc_vector_unchecked) for the safety
577    /// contract.
578    ///
579    /// # Safety
580    ///
581    /// Same as [`alloc_vector_unchecked`](Self::alloc_vector_unchecked).
582    #[inline]
583    pub unsafe fn alloc_character_unchecked<'a>(&'a self, n: usize) -> Root<'a> {
584        let len = R_xlen_t::try_from(n).expect("length exceeds R_xlen_t");
585        unsafe { self.alloc_vector_unchecked(SEXPTYPE::STRSXP, len) }
586    }
587
588    // region: Typed vector allocation shortcuts
589
590    /// Allocate an integer vector (INTSXP), protected.
591    ///
592    /// # Safety
593    ///
594    /// Must be called from the R main thread.
595    #[inline]
596    pub unsafe fn alloc_integer<'a>(&'a self, n: usize) -> Root<'a> {
597        let len = R_xlen_t::try_from(n).expect("length exceeds R_xlen_t");
598        unsafe { self.alloc_vector(SEXPTYPE::INTSXP, len) }
599    }
600
601    /// Allocate a real vector (REALSXP), protected.
602    ///
603    /// # Safety
604    ///
605    /// Must be called from the R main thread.
606    #[inline]
607    pub unsafe fn alloc_real<'a>(&'a self, n: usize) -> Root<'a> {
608        let len = R_xlen_t::try_from(n).expect("length exceeds R_xlen_t");
609        unsafe { self.alloc_vector(SEXPTYPE::REALSXP, len) }
610    }
611
612    /// Allocate a logical vector (LGLSXP), protected.
613    ///
614    /// # Safety
615    ///
616    /// Must be called from the R main thread.
617    #[inline]
618    pub unsafe fn alloc_logical<'a>(&'a self, n: usize) -> Root<'a> {
619        let len = R_xlen_t::try_from(n).expect("length exceeds R_xlen_t");
620        unsafe { self.alloc_vector(SEXPTYPE::LGLSXP, len) }
621    }
622
623    /// Allocate a raw vector (RAWSXP), protected.
624    ///
625    /// # Safety
626    ///
627    /// Must be called from the R main thread.
628    #[inline]
629    pub unsafe fn alloc_raw<'a>(&'a self, n: usize) -> Root<'a> {
630        let len = R_xlen_t::try_from(n).expect("length exceeds R_xlen_t");
631        unsafe { self.alloc_vector(SEXPTYPE::RAWSXP, len) }
632    }
633
634    /// Allocate a complex vector (CPLXSXP), protected.
635    ///
636    /// # Safety
637    ///
638    /// Must be called from the R main thread.
639    #[inline]
640    pub unsafe fn alloc_complex<'a>(&'a self, n: usize) -> Root<'a> {
641        let len = R_xlen_t::try_from(n).expect("length exceeds R_xlen_t");
642        unsafe { self.alloc_vector(SEXPTYPE::CPLXSXP, len) }
643    }
644
645    /// Allocate a character vector (STRSXP), protected.
646    ///
647    /// # Safety
648    ///
649    /// Must be called from the R main thread.
650    #[inline]
651    pub unsafe fn alloc_character<'a>(&'a self, n: usize) -> Root<'a> {
652        let len = R_xlen_t::try_from(n).expect("length exceeds R_xlen_t");
653        unsafe { self.alloc_vector(SEXPTYPE::STRSXP, len) }
654    }
655
656    // endregion
657
658    // region: Scalar constructors (allocate + set + protect)
659
660    /// Create a scalar integer (length-1 INTSXP), protected.
661    ///
662    /// # Safety
663    ///
664    /// Must be called from the R main thread.
665    #[inline]
666    pub unsafe fn scalar_integer<'a>(&'a self, x: i32) -> Root<'a> {
667        unsafe { self.protect(SEXP::scalar_integer(x)) }
668    }
669
670    /// Create a scalar real (length-1 REALSXP), protected.
671    ///
672    /// # Safety
673    ///
674    /// Must be called from the R main thread.
675    #[inline]
676    pub unsafe fn scalar_real<'a>(&'a self, x: f64) -> Root<'a> {
677        unsafe { self.protect(SEXP::scalar_real(x)) }
678    }
679
680    /// Create a scalar logical (length-1 LGLSXP), protected.
681    ///
682    /// # Safety
683    ///
684    /// Must be called from the R main thread.
685    #[inline]
686    pub unsafe fn scalar_logical<'a>(&'a self, x: bool) -> Root<'a> {
687        unsafe { self.protect(SEXP::scalar_logical(x)) }
688    }
689
690    /// Create a scalar complex (length-1 CPLXSXP), protected.
691    ///
692    /// # Safety
693    ///
694    /// Must be called from the R main thread.
695    #[inline]
696    pub unsafe fn scalar_complex<'a>(&'a self, x: crate::Rcomplex) -> Root<'a> {
697        unsafe { self.protect(SEXP::scalar_complex(x)) }
698    }
699
700    /// Create a scalar raw (length-1 RAWSXP), protected.
701    ///
702    /// # Safety
703    ///
704    /// Must be called from the R main thread.
705    #[inline]
706    pub unsafe fn scalar_raw<'a>(&'a self, x: u8) -> Root<'a> {
707        unsafe { self.protect(SEXP::scalar_raw(x)) }
708    }
709
710    /// Create a scalar string (length-1 STRSXP) from a Rust `&str`, protected.
711    ///
712    /// # Safety
713    ///
714    /// Must be called from the R main thread.
715    #[inline]
716    pub unsafe fn scalar_string<'a>(&'a self, s: &str) -> Root<'a> {
717        unsafe { self.protect(SEXP::scalar_string(SEXP::charsxp(s))) }
718    }
719
720    // endregion
721
722    // region: CHARSXP, duplication, coercion, environment
723
724    /// Create a CHARSXP from a Rust `&str`, protected.
725    ///
726    /// # Safety
727    ///
728    /// Must be called from the R main thread.
729    #[inline]
730    pub unsafe fn mkchar<'a>(&'a self, s: &str) -> Root<'a> {
731        unsafe { self.protect(SEXP::charsxp(s)) }
732    }
733
734    /// Deep-duplicate a SEXP, protected.
735    ///
736    /// # Safety
737    ///
738    /// Must be called from the R main thread. `x` must be a valid SEXP.
739    #[inline]
740    pub unsafe fn duplicate<'a>(&'a self, x: SEXP) -> Root<'a> {
741        unsafe { self.protect(x.duplicate()) }
742    }
743
744    /// Shallow-duplicate a SEXP, protected.
745    ///
746    /// # Safety
747    ///
748    /// Must be called from the R main thread. `x` must be a valid SEXP.
749    #[inline]
750    pub unsafe fn shallow_duplicate<'a>(&'a self, x: SEXP) -> Root<'a> {
751        unsafe { self.protect(x.shallow_duplicate()) }
752    }
753
754    /// Coerce a SEXP to a different type, protected.
755    ///
756    /// # Safety
757    ///
758    /// Must be called from the R main thread. `x` must be a valid SEXP.
759    #[inline]
760    pub unsafe fn coerce<'a>(&'a self, x: SEXP, target: SEXPTYPE) -> Root<'a> {
761        unsafe { self.protect(x.coerce(target)) }
762    }
763
764    /// Create a new environment, protected.
765    ///
766    /// # Safety
767    ///
768    /// Must be called from the R main thread.
769    #[inline]
770    pub unsafe fn new_env<'a>(&'a self, parent: SEXP, hash: bool, size: i32) -> Root<'a> {
771        unsafe {
772            self.protect(R_NewEnv(
773                parent,
774                if hash {
775                    crate::Rboolean::TRUE
776                } else {
777                    crate::Rboolean::FALSE
778                },
779                size,
780            ))
781        }
782    }
783
784    // endregion
785
786    // region: Array, pairlist, S4, and misc allocation helpers
787
788    /// Allocate an n-dimensional array of the given type, and immediately protect it.
789    ///
790    /// The `dims` slice is first allocated as an integer vector inside this scope
791    /// (consuming one protect slot) to satisfy `Rf_allocArray`'s SEXP-dims contract.
792    /// The resulting array SEXP consumes a second protect slot.
793    ///
794    /// # Protect-stack budget
795    ///
796    /// This helper consumes **two** protect slots — one for the dims vector and
797    /// one for the array — yet returns a single [`Root`] (for the array). The
798    /// dims INTSXP has no accessible handle: it stays protected until the scope
799    /// drops. Consequently [`count()`](Self::count) increases by 2 even though
800    /// the caller sees only one `Root`. Callers budgeting protect-stack depth
801    /// must account for both slots, not just the returned handle.
802    ///
803    /// # Safety
804    ///
805    /// Must be called from the R main thread. `dims` must be non-empty.
806    #[inline]
807    pub unsafe fn alloc_array<'a>(&'a self, ty: SEXPTYPE, dims: &[i32]) -> Root<'a> {
808        // Build the dims INTSXP inside the scope so it is protected across the
809        // Rf_allocArray call (which may trigger GC).
810        let dims_len = R_xlen_t::try_from(dims.len()).expect("dims length exceeds R_xlen_t");
811        let dims_sexp = unsafe { self.alloc_vector(SEXPTYPE::INTSXP, dims_len) };
812        for (i, &d) in dims.iter().enumerate() {
813            dims_sexp.get().set_integer_elt(i as isize, d);
814        }
815        let sexp = unsafe { Rf_allocArray(ty, dims_sexp.get()) };
816        unsafe { self.protect(sexp) }
817    }
818
819    /// Allocate a 3-dimensional array of the given type, and immediately protect it.
820    ///
821    /// # Safety
822    ///
823    /// Must be called from the R main thread.
824    #[inline]
825    pub unsafe fn alloc_3d_array<'a>(
826        &'a self,
827        ty: SEXPTYPE,
828        nrow: i32,
829        ncol: i32,
830        nface: i32,
831    ) -> Root<'a> {
832        let sexp = unsafe { Rf_alloc3DArray(ty, nrow, ncol, nface) };
833        unsafe { self.protect(sexp) }
834    }
835
836    /// Allocate a language object (LANGSXP) of the given length, and immediately protect it.
837    ///
838    /// # Safety
839    ///
840    /// Must be called from the R main thread.
841    #[inline]
842    pub unsafe fn alloc_lang<'a>(&'a self, n: i32) -> Root<'a> {
843        // Rf_allocLang was added in R 4.4.1. Use the equivalent construction
844        // from Writing R Extensions, "Some backports", also on R 4.4.0.
845        let sexp = unsafe {
846            if n > 0 {
847                crate::sys::Rf_lcons(crate::sys::R_NilValue, Rf_allocList(n - 1))
848            } else {
849                crate::sys::R_NilValue
850            }
851        };
852        unsafe { self.protect(sexp) }
853    }
854
855    /// Allocate an S4 object (S4SXP), and immediately protect it.
856    ///
857    /// # Safety
858    ///
859    /// Must be called from the R main thread.
860    #[inline]
861    pub unsafe fn alloc_s4_object<'a>(&'a self) -> Root<'a> {
862        let sexp = unsafe { Rf_allocS4Object() };
863        unsafe { self.protect(sexp) }
864    }
865
866    /// Create a CHARSXP with a specified encoding and byte length, protected.
867    ///
868    /// This is the general form; see [`mkchar`][Self::mkchar] for the plain UTF-8 shorthand.
869    ///
870    /// # Safety
871    ///
872    /// Must be called from the R main thread. `s` must be valid for `len` bytes.
873    #[inline]
874    pub unsafe fn mkchar_len_ce<'a>(&'a self, s: &[u8], enc: cetype_t) -> Root<'a> {
875        let len = i32::try_from(s.len()).expect("string length exceeds i32");
876        let sexp = unsafe { Rf_mkCharLenCE(s.as_ptr().cast::<::std::os::raw::c_char>(), len, enc) };
877        unsafe { self.protect(sexp) }
878    }
879
880    /// Create a CHARSXP from a Rust `&str` with a specified encoding, protected.
881    ///
882    /// This is the length-counted variant (wraps `Rf_mkCharLenCE`); it is safe for
883    /// strings with embedded NUL bytes and does not require a NUL terminator.
884    /// For the raw NUL-terminated `Rf_mkCharCE` form, use
885    /// [`mkchar_len_ce`][Self::mkchar_len_ce] with `s.as_bytes()`.
886    ///
887    /// # Safety
888    ///
889    /// Must be called from the R main thread.
890    #[inline]
891    pub unsafe fn mkchar_ce<'a>(&'a self, s: &str, enc: cetype_t) -> Root<'a> {
892        let len = i32::try_from(s.len()).expect("string length exceeds i32");
893        let sexp = unsafe { Rf_mkCharLenCE(s.as_ptr().cast::<::std::os::raw::c_char>(), len, enc) };
894        unsafe { self.protect(sexp) }
895    }
896
897    /// Construct a pairlist cons cell (`LISTSXP`) and immediately protect it.
898    ///
899    /// Allocates a single cons cell with `car` as the head and `cdr` as the tail.
900    /// Both `car` and `cdr` must already be protected.
901    ///
902    /// # Safety
903    ///
904    /// Must be called from the R main thread. `car` and `cdr` must be valid SEXPs.
905    #[inline]
906    pub unsafe fn cons<'a>(&'a self, car: SEXP, cdr: SEXP) -> Root<'a> {
907        let sexp = unsafe { Rf_cons(car, cdr) };
908        unsafe { self.protect(sexp) }
909    }
910
911    /// Construct a language cons cell (`LANGSXP`) and immediately protect it.
912    ///
913    /// Like [`cons`][Self::cons] but marks the node as `LANGSXP` (function call list).
914    ///
915    /// # Safety
916    ///
917    /// Must be called from the R main thread. `car` and `cdr` must be valid SEXPs.
918    #[inline]
919    pub unsafe fn lcons<'a>(&'a self, car: SEXP, cdr: SEXP) -> Root<'a> {
920        let sexp = unsafe { Rf_lcons(car, cdr) };
921        unsafe { self.protect(sexp) }
922    }
923
924    /// Resize a vector to a new length (short-vector variant), returning a protected copy.
925    ///
926    /// Wraps `Rf_lengthgets`. Both the source vector and the resized copy should be
927    /// protected; the copy is protected by this call.
928    ///
929    /// # Safety
930    ///
931    /// Must be called from the R main thread. `x` must be a valid vector SEXP.
932    #[inline]
933    pub unsafe fn lengthgets<'a>(&'a self, x: SEXP, n: R_xlen_t) -> Root<'a> {
934        let sexp = unsafe { Rf_lengthgets(x, n) };
935        unsafe { self.protect(sexp) }
936    }
937
938    /// Resize a vector to a new length (long-vector variant), returning a protected copy.
939    ///
940    /// Like [`lengthgets`][Self::lengthgets] but uses `Rf_xlengthgets`, which accepts
941    /// `R_xlen_t` lengths beyond `INT_MAX`.
942    ///
943    /// # Safety
944    ///
945    /// Must be called from the R main thread. `x` must be a valid vector SEXP.
946    #[inline]
947    pub unsafe fn xlengthgets<'a>(&'a self, x: SEXP, n: R_xlen_t) -> Root<'a> {
948        let sexp = unsafe { Rf_xlengthgets(x, n) };
949        unsafe { self.protect(sexp) }
950    }
951
952    /// Create an external pointer SEXP, and immediately protect it.
953    ///
954    /// This is an escape-hatch tier wrapper around `R_MakeExternalPtr`. In most
955    /// cases you should use [`ExternalPtr<T>`](struct@crate::ExternalPtr) instead — it
956    /// provides typed safety, finalizer registration, and correct rooting (#841).
957    /// Use this wrapper only when you need raw `EXTPTRSXP` construction that
958    /// `ExternalPtr<T>` cannot express.
959    ///
960    /// Both `tag` and `prot` must be valid SEXPs (pass `R_NilValue` if unused).
961    ///
962    /// # Safety
963    ///
964    /// Must be called from the R main thread. `p`, `tag`, and `prot` must all be
965    /// valid for the duration of the external pointer's lifetime.
966    #[inline]
967    pub unsafe fn make_external_ptr<'a>(
968        &'a self,
969        p: *mut ::std::os::raw::c_void,
970        tag: SEXP,
971        prot: SEXP,
972    ) -> Root<'a> {
973        let sexp = unsafe { R_MakeExternalPtr(p, tag, prot) };
974        unsafe { self.protect(sexp) }
975    }
976
977    // endregion
978
979    /// Create a `Root<'a>` for an already-protected SEXP without adding protection.
980    ///
981    /// This is useful when you have a SEXP that is already protected by some other
982    /// mechanism (e.g., a `ReprotectSlot`, or a `.Call` argument, which R roots
983    /// until the call returns) and want to return it as a `Root` tied to this
984    /// scope's lifetime. Generated wrappers bind borrowed parameters this way.
985    ///
986    /// # Safety
987    ///
988    /// - The caller must ensure `sexp` is already protected and will remain
989    ///   protected for at least the lifetime of this scope
990    /// - Must be called from the R main thread
991    #[inline]
992    pub unsafe fn rooted<'a>(&'a self, sexp: SEXP) -> Root<'a> {
993        Root { sexp, scope: self }
994    }
995    // endregion
996
997    // region: Iterator Collection
998
999    /// Collect an iterator into a typed R vector.
1000    ///
1001    /// This allocates once, protects, and fills directly - the most efficient pattern
1002    /// for typed vectors. The element type `T` determines the R vector type via
1003    /// the [`RNativeType`] trait.
1004    ///
1005    /// # Type Mapping
1006    ///
1007    /// | Rust Type | R Vector Type |
1008    /// |-----------|---------------|
1009    /// | `i32` | `INTSXP` |
1010    /// | `f64` | `REALSXP` |
1011    /// | `u8` | `RAWSXP` |
1012    /// | [`RLogical`](crate::RLogical) | `LGLSXP` |
1013    /// | [`Rcomplex`](crate::Rcomplex) | `CPLXSXP` |
1014    ///
1015    /// # Safety
1016    ///
1017    /// Must be called from the R main thread.
1018    ///
1019    /// # Example
1020    ///
1021    /// ```ignore
1022    /// unsafe fn squares(n: usize) -> SEXP {
1023    ///     let scope = ProtectScope::new();
1024    ///     // Type inferred from iterator
1025    ///     scope.collect((0..n).map(|i| (i * i) as i32)).get()
1026    /// }
1027    /// ```
1028    ///
1029    /// # Unknown Length
1030    ///
1031    /// For iterators without exact size (e.g., `filter`), collect to `Vec` first:
1032    ///
1033    /// ```ignore
1034    /// let evens: Vec<i32> = data.iter().filter(|x| *x % 2 == 0).copied().collect();
1035    /// scope.collect(evens)
1036    /// ```
1037    #[inline]
1038    pub unsafe fn collect<'a, T, I>(&'a self, iter: I) -> Root<'a>
1039    where
1040        T: RNativeType,
1041        I: IntoIterator<Item = T>,
1042        I::IntoIter: ExactSizeIterator,
1043    {
1044        let iter = iter.into_iter();
1045        let len = iter.len();
1046
1047        let vec = unsafe { self.alloc_vector(T::SEXP_TYPE, len as R_xlen_t) };
1048        let ptr = unsafe { T::dataptr_mut(vec.get()) };
1049
1050        for (i, value) in iter.enumerate() {
1051            unsafe { ptr.add(i).write(value) };
1052        }
1053
1054        vec
1055    }
1056}
1057
1058impl Drop for ProtectScope {
1059    #[inline]
1060    fn drop(&mut self) {
1061        if !self.armed.get() {
1062            return;
1063        }
1064        let n = self.n.replace(0);
1065        if n > 0 {
1066            unsafe { Rf_unprotect(n) };
1067        }
1068    }
1069}
1070
1071// endregion
1072
1073// region: Root
1074
1075/// Lazy retention of uncached ALTREP elements, shared by call scopes and
1076/// owned views. Ordinary vectors already trace their elements and need no pool.
1077#[derive(#[automatically_derived]
impl ::core::default::Default for ChildRoots {
    #[inline]
    fn default() -> Self { ChildRoots(::core::default::Default::default()) }
}Default)]
1078pub(crate) struct ChildRoots(RefCell<Option<Box<crate::protect_pool::ProtectPool>>>);
1079
1080impl ChildRoots {
1081    /// Keep `child` alive until this storage drops.
1082    ///
1083    /// # Safety
1084    /// R's main thread; `parent` and `child` must be live on entry. An ordinary
1085    /// parent must remain rooted while its returned children are in use.
1086    pub(crate) unsafe fn protect(&self, parent: SEXP, child: SEXP) -> SEXP {
1087        if !parent.is_altrep() {
1088            return child;
1089        }
1090        let mut children = self.0.borrow_mut();
1091        let pool = children.get_or_insert_with(|| {
1092            // Creating the pool allocates before it retains the child. Its
1093            // inserts keep the child reachable while the pool grows.
1094            let _child = unsafe { OwnedProtect::new(child) };
1095            Box::new(crate::protect_pool::ProtectPool::new(
1096                crate::protect_pool::ProtectPool::DEFAULT_CAPACITY,
1097            ))
1098        });
1099        pool.root(child)
1100    }
1101}
1102
1103/// A rooted SEXP tied to the lifetime of a [`ProtectScope`].
1104///
1105/// This type has **no `Drop`**. The scope owns unprotection responsibility.
1106/// This makes `Root` cheap to move and copy (the SEXP and its scope reference).
1107///
1108/// # Lifetime
1109///
1110/// The `'a` lifetime ties the root to its creating scope. The compiler ensures
1111/// you cannot use the root after the scope has been dropped.
1112#[derive(#[automatically_derived]
#[doc(hidden)]
unsafe impl<'a> ::core::clone::TrivialClone for Root<'a> { }
#[automatically_derived]
impl<'a> ::core::clone::Clone for Root<'a> {
    #[inline]
    fn clone(&self) -> Self {
        let _: ::core::clone::AssertParamIsClone<SEXP>;
        let _: ::core::clone::AssertParamIsClone<&'a ProtectScope>;
        *self
    }
}Clone, #[automatically_derived]
impl<'a> ::core::marker::Copy for Root<'a> { }Copy)]
1113pub struct Root<'a> {
1114    sexp: SEXP,
1115    scope: &'a ProtectScope,
1116}
1117
1118impl<'a> Root<'a> {
1119    /// Root `child`, an element read from this root, for this root's lifetime.
1120    ///
1121    /// An ordinary vector holds its elements, so its root already covers them.
1122    /// An ALTREP accessor may return a fresh object that its parent does not
1123    /// keep; the scope then retains it in a lazily allocated pool until the
1124    /// scope drops, without a PROTECT stack entry per element.
1125    ///
1126    /// # Safety
1127    /// R's main thread; `child` must be live on entry.
1128    pub(crate) unsafe fn protect_child(self, child: SEXP) -> Self {
1129        Root {
1130            sexp: unsafe { self.scope.children.protect(self.sexp, child) },
1131            scope: self.scope,
1132        }
1133    }
1134
1135    /// Retention storage borrowed by views that read their elements lazily.
1136    pub(crate) fn child_roots(self) -> &'a ChildRoots {
1137        &self.scope.children
1138    }
1139
1140    /// Get the underlying SEXP.
1141    #[inline]
1142    pub fn get(&self) -> SEXP {
1143        self.sexp
1144    }
1145
1146    /// Consume the root and return the underlying SEXP.
1147    ///
1148    /// The SEXP remains protected until the scope drops.
1149    #[inline]
1150    pub fn into_raw(self) -> SEXP {
1151        self.sexp
1152    }
1153}
1154
1155impl<'a> std::ops::Deref for Root<'a> {
1156    type Target = SEXP;
1157
1158    #[inline]
1159    fn deref(&self) -> &Self::Target {
1160        &self.sexp
1161    }
1162}
1163// endregion
1164
1165// region: OwnedProtect
1166
1167/// A single-object RAII guard: `PROTECT` on create, `UNPROTECT(1)` on drop.
1168///
1169/// Use this for simple cases where you're protecting a single value and
1170/// don't need the batching benefits of [`ProtectScope`].
1171///
1172/// # Example
1173///
1174/// ```ignore
1175/// unsafe fn allocate_and_fill() -> SEXP {
1176///     let guard = OwnedProtect::new(Rf_allocVector(REALSXP, 10));
1177///     fill_vector(guard.get());
1178///     // Return the SEXP - guard drops and unprotects on this line.
1179///     // This is safe because no GC can occur between unprotect and return.
1180///     guard.get()
1181/// }
1182/// ```
1183///
1184/// # Warning: Stack Ordering
1185///
1186/// `OwnedProtect` uses `UNPROTECT(1)`, which removes the **top** of the protection
1187/// stack. If you have nested protections from other sources, the drop order matters!
1188///
1189/// For complex scenarios, prefer [`ProtectScope`] which unprotects all its values
1190/// at once when dropped.
1191pub struct OwnedProtect {
1192    sexp: SEXP,
1193    armed: bool,
1194    _nosend: NoSendSync,
1195}
1196
1197impl OwnedProtect {
1198    /// Create a new protection guard for `x`.
1199    ///
1200    /// Calls `Rf_protect(x)` immediately.
1201    ///
1202    /// # Safety
1203    ///
1204    /// - Must be called from the R main thread
1205    /// - `x` must be a valid SEXP
1206    #[inline]
1207    pub unsafe fn new(x: SEXP) -> Self {
1208        let y = unsafe { Rf_protect(x) };
1209        Self {
1210            sexp: y,
1211            armed: true,
1212            _nosend: PhantomData,
1213        }
1214    }
1215
1216    /// Get the protected SEXP.
1217    #[inline]
1218    pub fn get(&self) -> SEXP {
1219        self.sexp
1220    }
1221
1222    /// Escape hatch: do not `UNPROTECT(1)` on drop.
1223    ///
1224    /// # Safety
1225    ///
1226    /// Leaks one protection entry unless unprotected elsewhere.
1227    #[inline]
1228    pub unsafe fn forget(mut self) {
1229        self.armed = false;
1230        core::mem::forget(self);
1231    }
1232}
1233
1234impl Drop for OwnedProtect {
1235    #[inline]
1236    fn drop(&mut self) {
1237        if self.armed {
1238            unsafe { Rf_unprotect(1) };
1239        }
1240    }
1241}
1242
1243impl std::ops::Deref for OwnedProtect {
1244    type Target = SEXP;
1245
1246    #[inline]
1247    fn deref(&self) -> &Self::Target {
1248        &self.sexp
1249    }
1250}
1251// endregion
1252
1253// region: Protected
1254
1255/// A Rust value (`T`) bundled with an [`OwnedProtect`] guard on an SEXP
1256/// the value borrows from. The protect releases on drop; the lifetime
1257/// ties any borrows inside `T` to `&self`, so `T`'s SEXP-internal
1258/// references can't outlive the protection.
1259///
1260/// # When to use `Protected<'a, T>` vs the alternatives
1261///
1262/// | Pattern | Use | Why |
1263/// |---------|-----|-----|
1264/// | [`OwnedProtect`] | raw SEXP, no Rust view | one-shot protect/unprotect on a single SEXP |
1265/// | [`ProtectScope`] + [`Root`] | several SEXPs in one function body | batched UNPROTECT, no Rust view |
1266/// | `Protected<'a, T>` | SEXP + Rust view of its data | pairs a root with `T`; does not constrain references inside `T` |
1267///
1268/// # Lifetimes and thread affinity
1269///
1270/// This generic bundle does not shorten lifetimes inside `T`. Its callers must
1271/// avoid exposing an inner borrowed view after the root is released; use a
1272/// concrete owner with accessors tied to `&self` (such as
1273/// [`ProtectedStrVec`](crate::ProtectedStrVec)) for that guarantee.
1274/// `Option<OwnedProtect>` is `!Send + !Sync` even when its value is `None`;
1275/// the trusted constructor does not change those auto-trait properties.
1276pub struct Protected<'a, T> {
1277    inner: T,
1278    _protect: Option<OwnedProtect>,
1279    _marker: core::marker::PhantomData<&'a ()>,
1280}
1281
1282impl<'a, T> Protected<'a, T> {
1283    /// Create a protected bundle. Calls `Rf_protect` on `sexp`.
1284    ///
1285    /// `inner` may borrow from `sexp`, but this bundle does not rewrite those
1286    /// lifetimes. The caller must ensure any references copied from `inner`
1287    /// remain valid, including after this root is dropped.
1288    ///
1289    /// # Safety
1290    ///
1291    /// - Must be called from the R main thread.
1292    /// - `sexp` must be a valid SEXP.
1293    /// - Borrowed data in `inner` must remain valid for its declared lifetime;
1294    ///   this guard alone cannot guarantee that when `T` exposes a longer
1295    ///   reference (for example, `T = &'static str`).
1296    ///
1297    /// # Example
1298    ///
1299    /// ```ignore
1300    /// use miniextendr_api::{Protected, OwnedProtect};
1301    /// use miniextendr_api::prelude::SEXP;
1302    ///
1303    /// unsafe fn wrap_view(sexp: SEXP, view: MyView<'_>) -> Protected<'_, MyView<'_>> {
1304    ///     // Protect the SEXP and bundle it with the view.
1305    ///     // The caller must keep any references exposed by MyView valid.
1306    ///     Protected::new(sexp, view)
1307    /// }
1308    /// ```
1309    #[inline]
1310    pub unsafe fn new(sexp: SEXP, inner: T) -> Self {
1311        let guard = unsafe { OwnedProtect::new(sexp) };
1312        Self {
1313            inner,
1314            _protect: Some(guard),
1315            _marker: core::marker::PhantomData,
1316        }
1317    }
1318
1319    /// Create a protected bundle without adding to the protect stack.
1320    ///
1321    /// Use when `sexp` is already protected by R (a `.Call` argument,
1322    /// a [`ProtectScope`] slot, an enclosing [`OwnedProtect`]) to avoid
1323    /// double-protecting. As with `new`, `'a` does not narrow references
1324    /// contained in `T`.
1325    ///
1326    /// # Safety
1327    ///
1328    /// - Must be called from the R main thread.
1329    /// - `sexp` must be a valid SEXP.
1330    /// - `sexp` must remain GC-protected for the lifetime of the
1331    ///   returned `Protected`.
1332    /// - Lifetime contract same as [`Protected::new`].
1333    #[inline]
1334    pub unsafe fn from_trusted(_sexp: SEXP, inner: T) -> Self {
1335        Self {
1336            inner,
1337            _protect: None,
1338            _marker: core::marker::PhantomData,
1339        }
1340    }
1341
1342    /// Borrow the inner view.
1343    #[inline]
1344    pub fn get(&self) -> &T {
1345        &self.inner
1346    }
1347
1348    /// Consume the bundle and return the inner view.
1349    ///
1350    /// The [`OwnedProtect`] guard drops here, releasing the SEXP from the protect
1351    /// stack. Any owned data extracted from `T` must not retain SEXP references
1352    /// after this point.
1353    #[inline]
1354    pub fn into_inner(self) -> T {
1355        self.inner
1356    }
1357}
1358
1359impl<'a, T> core::ops::Deref for Protected<'a, T> {
1360    type Target = T;
1361
1362    #[inline]
1363    fn deref(&self) -> &T {
1364        &self.inner
1365    }
1366}
1367
1368impl<'a, T: std::fmt::Debug> std::fmt::Debug for Protected<'a, T> {
1369    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1370        f.debug_struct("Protected")
1371            .field("inner", &self.inner)
1372            .field("protected", &self._protect.is_some())
1373            .finish()
1374    }
1375}
1376// endregion
1377
1378// region: ReprotectSlot
1379
1380/// A protected slot created with `R_ProtectWithIndex` and updated with `R_Reprotect`.
1381///
1382/// This allows updating a protected value in-place without growing the protection
1383/// stack. Useful for loops that repeatedly allocate and update a value.
1384///
1385/// The slot is valid only while the creating [`ProtectScope`] is alive.
1386///
1387/// # When to Use `ReprotectSlot`
1388///
1389/// Use `ReprotectSlot` when you need to **reassign a protected value** multiple times:
1390///
1391/// | Pattern | Use | Why |
1392/// |---------|-----|-----|
1393/// | Accumulator loop | `ReprotectSlot` | Repeatedly replace result without stack growth |
1394/// | Single allocation | `ProtectScope::protect` | Simpler, no reassignment needed |
1395/// | Child insertion | `List::set_elt` | Container handles child protection |
1396///
1397/// # Warning: RAII Assignment Pitfall
1398///
1399/// R's PROTECT stack is LIFO. Rust's RAII drop order can cause problems:
1400///
1401/// ```ignore
1402/// // WRONG - can unprotect the new value instead of the old!
1403/// let mut guard = OwnedProtect::new(old_value);
1404/// guard = OwnedProtect::new(new_value);  // Old guard drops AFTER new is assigned
1405/// ```
1406///
1407/// `ReprotectSlot` avoids this by using `R_Reprotect` which replaces in-place:
1408///
1409/// ```ignore
1410/// // CORRECT - always keeps exactly one slot protected
1411/// let slot = scope.protect_with_index(old_value);
1412/// slot.set(new_value);  // R_Reprotect, no stack change
1413/// ```
1414///
1415/// # Examples
1416///
1417/// ## Accumulator Pattern
1418///
1419/// ```ignore
1420/// unsafe fn sum_allocated_vectors(n: i32) -> SEXP {
1421///     let scope = ProtectScope::new();
1422///
1423///     // Initial allocation
1424///     let slot = scope.protect_with_index(Rf_allocVector(REALSXP, 10));
1425///
1426///     for i in 0..n {
1427///         // Each iteration allocates a new vector
1428///         let new_vec = compute_step(slot.get(), i);
1429///         slot.set(new_vec);  // Replace without growing protect stack
1430///     }
1431///
1432///     slot.get()
1433/// }
1434/// ```
1435///
1436/// ## Starting with Empty Slot
1437///
1438/// ```ignore
1439/// unsafe fn build_result(items: &[Input]) -> SEXP {
1440///     let scope = ProtectScope::new();
1441///
1442///     // Start with R_NilValue, replace with first real result
1443///     let slot = scope.protect_with_index(R_NilValue);
1444///
1445///     for (i, item) in items.iter().enumerate() {
1446///         let result = process_item(item, slot.get());
1447///         slot.set(result);
1448///     }
1449///
1450///     slot.get()
1451/// }
1452/// ```
1453///
1454/// ## Multiple Slots
1455///
1456/// ```ignore
1457/// unsafe fn merge_sorted(a: SEXP, b: SEXP) -> SEXP {
1458///     let scope = ProtectScope::new();
1459///
1460///     let slot_a = scope.protect_with_index(a);
1461///     let slot_b = scope.protect_with_index(b);
1462///     let result = scope.protect_with_index(R_NilValue);
1463///
1464///     // Process both inputs, updating result
1465///     while !is_empty(slot_a.get()) && !is_empty(slot_b.get()) {
1466///         let merged = merge_next(slot_a.get(), slot_b.get());
1467///         result.set(merged);
1468///         // ... update slot_a and slot_b as needed
1469///     }
1470///
1471///     result.get()
1472/// }
1473/// ```
1474pub struct ReprotectSlot<'a> {
1475    idx: ProtectIndex,
1476    cur: Cell<SEXP>,
1477    _scope: PhantomData<&'a ProtectScope>,
1478    _nosend: NoSendSync,
1479}
1480
1481impl<'a> ReprotectSlot<'a> {
1482    /// Get the currently protected SEXP.
1483    #[inline]
1484    pub fn get(&self) -> SEXP {
1485        self.cur.get()
1486    }
1487
1488    /// Replace the protected value in-place using `R_Reprotect`.
1489    ///
1490    /// The new value `x` becomes protected in this slot, and the old value
1491    /// is no longer protected (but may still be rooted elsewhere).
1492    ///
1493    /// Returns the raw SEXP for convenience. Note that this SEXP is only
1494    /// protected until the next call to `set()` on this slot - if you need
1495    /// to hold multiple protected values simultaneously, use separate
1496    /// protection slots or `OwnedProtect`.
1497    ///
1498    /// # Safety
1499    ///
1500    /// - Must be called from the R main thread
1501    /// - `x` must be a valid SEXP
1502    #[inline]
1503    pub unsafe fn set(&self, x: SEXP) -> SEXP {
1504        unsafe { R_Reprotect(x, self.idx) };
1505        self.cur.set(x);
1506        x
1507    }
1508
1509    /// Allocate a new value via the closure and replace this slot's value safely.
1510    ///
1511    /// This method encodes the safe pattern for replacing a protected slot with
1512    /// a newly allocated value. It:
1513    ///
1514    /// 1. Calls the closure `f()` to allocate a new SEXP
1515    /// 2. Temporarily protects the new value (to close the GC gap)
1516    /// 3. Calls `R_Reprotect` to replace this slot's value
1517    /// 4. Unprotects the temporary protection
1518    ///
1519    /// This prevents the GC gap that would exist if you called `f()` and then
1520    /// `set()` separately - during that window, the newly allocated value would
1521    /// be unprotected.
1522    ///
1523    /// # Safety
1524    ///
1525    /// - Must be called from the R main thread
1526    /// - The closure must return a valid SEXP
1527    ///
1528    /// # Example
1529    ///
1530    /// ```ignore
1531    /// unsafe fn grow_list(scope: &ProtectScope, old_list: SEXP) -> SEXP {
1532    ///     let slot = scope.protect_with_index(old_list);
1533    ///
1534    ///     // Safely grow the list without GC gap
1535    ///     slot.set_with(|| {
1536    ///         let new_list = Rf_allocVector(VECSXP, new_size);
1537    ///         // copy elements from old_list to new_list...
1538    ///         new_list
1539    ///     });
1540    ///
1541    ///     slot.get()
1542    /// }
1543    /// ```
1544    #[inline]
1545    pub unsafe fn set_with<F>(&self, f: F) -> SEXP
1546    where
1547        F: FnOnce() -> SEXP,
1548    {
1549        // Allocate the new value
1550        let new_value = f();
1551
1552        // Temporarily protect the new value to close the GC gap
1553        let temp = unsafe { Rf_protect(new_value) };
1554
1555        // Replace this slot's value with the new value
1556        unsafe { R_Reprotect(temp, self.idx) };
1557        self.cur.set(temp);
1558
1559        // Remove the temporary protection (slot now owns the protection)
1560        unsafe { Rf_unprotect(1) };
1561
1562        temp
1563    }
1564
1565    /// Take the current value and clear the slot to `R_NilValue`.
1566    ///
1567    /// This provides `Option::take`-like semantics. The slot remains allocated
1568    /// (protect stack depth unchanged), but now holds `R_NilValue` (immortal).
1569    ///
1570    /// # Safety
1571    ///
1572    /// - Must be called from the R main thread
1573    /// - The returned SEXP is **unprotected**. If it needs to survive further
1574    ///   allocations, you must protect it explicitly.
1575    ///
1576    /// # Example
1577    ///
1578    /// ```ignore
1579    /// let slot = scope.protect_with_index(some_value);
1580    /// // ... work with slot.get() ...
1581    /// let old = slot.take();  // slot now holds R_NilValue
1582    /// // old is unprotected - protect it if needed
1583    /// let guard = OwnedProtect::new(old);
1584    /// ```
1585    #[inline]
1586    pub unsafe fn take(&self) -> SEXP {
1587        let old = self.cur.get();
1588        let nil = SEXP::nil();
1589        unsafe { R_Reprotect(nil, self.idx) };
1590        self.cur.set(nil);
1591        old
1592    }
1593
1594    /// Replace the slot's value with `x` and return the old value.
1595    ///
1596    /// This provides `Option::replace`-like semantics. The slot now protects
1597    /// `x`, and the old value is returned **unprotected**.
1598    ///
1599    /// # Safety
1600    ///
1601    /// - Must be called from the R main thread
1602    /// - `x` must be a valid SEXP
1603    /// - The returned SEXP is **unprotected**. If it needs to survive further
1604    ///   allocations, you must protect it explicitly.
1605    ///
1606    /// # Example
1607    ///
1608    /// ```ignore
1609    /// let slot = scope.protect_with_index(initial);
1610    /// let old = slot.replace(new_value);
1611    /// // old is unprotected, slot now protects new_value
1612    /// ```
1613    #[inline]
1614    pub unsafe fn replace(&self, x: SEXP) -> SEXP {
1615        let old = self.cur.get();
1616        unsafe { R_Reprotect(x, self.idx) };
1617        self.cur.set(x);
1618        old
1619    }
1620
1621    /// Clear the slot by setting it to `R_NilValue`.
1622    ///
1623    /// The slot remains allocated (protect stack depth unchanged), but releases
1624    /// its reference to the previous value. The previous value may still be
1625    /// rooted elsewhere.
1626    ///
1627    /// # Safety
1628    ///
1629    /// Must be called from the R main thread.
1630    #[inline]
1631    pub unsafe fn clear(&self) {
1632        let nil = SEXP::nil();
1633        unsafe { R_Reprotect(nil, self.idx) };
1634        self.cur.set(nil);
1635    }
1636
1637    /// Check if the slot is currently cleared (holds `R_NilValue`).
1638    ///
1639    /// # Safety
1640    ///
1641    /// Must be called from the R main thread (accesses R's `R_NilValue`).
1642    #[inline]
1643    pub unsafe fn is_nil(&self) -> bool {
1644        self.cur.get() == SEXP::nil()
1645    }
1646}
1647
1648// NOTE: Deref was intentionally removed to avoid UB.
1649// The previous impl fabricated `&SEXP` from `Cell<SEXP>` via pointer cast,
1650// which violates Cell's aliasing rules if `set()` is called while a
1651// reference is live. Use `get()` instead, which returns SEXP by value.
1652// endregion
1653
1654pub mod tls;
1655
1656// region: WorkerUnprotectGuard — Send-safe unprotect for worker threads
1657
1658/// A `Send`-safe guard that calls `Rf_unprotect(n)` on drop via `with_r_thread`.
1659///
1660/// Use this when you `Rf_protect` on the R main thread, then need the unprotect
1661/// to happen when a guard drops on a **worker thread** (e.g., rayon parallel code).
1662///
1663/// [`OwnedProtect`] and [`ProtectScope`] are `!Send` — they can only be used on
1664/// the R main thread. `WorkerUnprotectGuard` fills the gap for cross-thread patterns
1665/// where allocation + protect happen on the R thread but the guard lives on a worker.
1666///
1667/// # Example
1668///
1669/// ```ignore
1670/// use miniextendr_api::gc_protect::WorkerUnprotectGuard;
1671///
1672/// let sexp = with_r_thread(|| unsafe {
1673///     let sexp = Rf_allocVector(REALSXP, n);
1674///     Rf_protect(sexp);
1675///     sexp
1676/// });
1677/// let _guard = WorkerUnprotectGuard::new(1);
1678///
1679/// // ... parallel work on sexp's data ...
1680/// // _guard drops here, dispatching Rf_unprotect(1) back to R thread
1681/// ```
1682pub struct WorkerUnprotectGuard(i32);
1683
1684impl WorkerUnprotectGuard {
1685    /// Create a guard that will unprotect `n` entries on drop.
1686    #[inline]
1687    pub fn new(n: i32) -> Self {
1688        Self(n)
1689    }
1690}
1691
1692impl Drop for WorkerUnprotectGuard {
1693    fn drop(&mut self) {
1694        let n = self.0;
1695        crate::worker::with_r_thread(move || unsafe {
1696            crate::sys::Rf_unprotect_unchecked(n);
1697        });
1698    }
1699}
1700
1701// Safety: no SEXP field, just an integer count. The actual Rf_unprotect call
1702// is dispatched to the R main thread via with_r_thread.
1703unsafe impl Send for WorkerUnprotectGuard {}
1704// endregion
1705
1706// region: Typed Vector Collection
1707
1708// NOTE: Typed vectors (INTSXP, REALSXP, RAWSXP, LGLSXP, CPLXSXP) do NOT need
1709// complex protection patterns during construction. You allocate once, protect
1710// once, then fill by writing directly to the data pointer. No GC can occur
1711// during the fill because you're just doing pointer writes - no R allocations.
1712//
1713// Only STRSXP (character vectors) and VECSXP (lists) need the ReprotectSlot
1714// pattern because each element insertion might allocate (mkChar, etc.).
1715//
1716// For typed vectors with unknown length, just collect to Vec<T> first, then
1717// allocate the exact size. The brief doubling of memory is fine.
1718// endregion
1719
1720// region: Tests
1721
1722#[cfg(test)]
1723mod tests {
1724    use super::*;
1725
1726    // Note: These tests primarily verify compilation and basic invariants.
1727    // Full integration testing requires R to be initialized.
1728    // endregion
1729
1730    // region: Basic invariants
1731
1732    #[test]
1733    fn protect_scope_has_nosend_marker() {
1734        // Verify the NoSendSync marker type is present
1735        // (ProtectScope contains PhantomData<Rc<()>> which makes it !Send + !Sync)
1736        let _: NoSendSync = PhantomData;
1737    }
1738
1739    #[test]
1740    fn protect_scope_default_count_is_zero() {
1741        let scope = unsafe { ProtectScope::new() };
1742        assert_eq!(scope.count(), 0);
1743    }
1744
1745    #[test]
1746    fn root_is_copy() {
1747        fn assert_copy<T: Copy>() {}
1748        assert_copy::<Root<'static>>();
1749    }
1750
1751    #[test]
1752    fn tls_root_is_copy() {
1753        fn assert_copy<T: Copy>() {}
1754        assert_copy::<tls::TlsRoot>();
1755    }
1756    // endregion
1757
1758    // region: Threading: compile-time !Send + !Sync checks
1759
1760    #[test]
1761    fn protect_scope_is_not_send() {
1762        fn assert_not_send<T>()
1763        where
1764            T: ?Sized,
1765        {
1766            // This test passes if ProtectScope is !Send
1767            // We can't directly assert !Send, but the type containing Rc<()> ensures it
1768        }
1769        assert_not_send::<ProtectScope>();
1770    }
1771
1772    #[test]
1773    fn protect_scope_is_not_sync() {
1774        fn assert_not_sync<T>()
1775        where
1776            T: ?Sized,
1777        {
1778            // This test passes if ProtectScope is !Sync
1779        }
1780        assert_not_sync::<ProtectScope>();
1781    }
1782
1783    #[test]
1784    fn owned_protect_is_not_send() {
1785        fn assert_not_send<T>()
1786        where
1787            T: ?Sized,
1788        {
1789        }
1790        assert_not_send::<OwnedProtect>();
1791    }
1792
1793    // Note: We can't easily assert !Send/!Sync at compile time without
1794    // negative trait bounds. The PhantomData<Rc<()>> marker ensures these types
1795    // are !Send and !Sync. If you need compile-time verification, use the
1796    // static_assertions crate with `assert_not_impl_any!`.
1797    // endregion
1798
1799    // region: TLS scope tests
1800
1801    #[test]
1802    fn tls_no_active_scope_by_default() {
1803        assert!(!tls::has_active_scope());
1804        assert_eq!(tls::current_count(), None);
1805        assert_eq!(tls::scope_depth(), 0);
1806    }
1807
1808    #[test]
1809    fn tls_scope_depth_tracking() {
1810        // Without R, we can only test the TLS tracking logic
1811        // The actual protect/unprotect requires R runtime
1812
1813        // Test that scope depth is tracked correctly
1814        assert_eq!(tls::scope_depth(), 0);
1815
1816        // We can't fully test with_protect_scope without R initialized,
1817        // but we can verify the API compiles and the TLS logic works
1818    }
1819
1820    #[test]
1821    #[should_panic(expected = "tls::protect called outside of with_protect_scope")]
1822    fn tls_protect_panics_outside_scope() {
1823        // This should panic because there's no active scope
1824        // Note: Can't actually call protect without R, but we test the panic message
1825        unsafe {
1826            let _ = tls::protect(crate::SEXP(std::ptr::null_mut()));
1827        }
1828    }
1829    // endregion
1830
1831    // region: Escape hatch tests
1832
1833    #[test]
1834    fn disarm_prevents_unprotect() {
1835        let scope = unsafe { ProtectScope::new() };
1836        assert!(scope.armed.get());
1837
1838        unsafe { scope.disarm() };
1839        assert!(!scope.armed.get());
1840
1841        // Scope will drop without calling Rf_unprotect (can't test actual R call)
1842    }
1843
1844    #[test]
1845    fn rearm_restores_unprotect() {
1846        let scope = unsafe { ProtectScope::new() };
1847
1848        unsafe {
1849            scope.disarm();
1850            assert!(!scope.armed.get());
1851
1852            scope.rearm();
1853            assert!(scope.armed.get());
1854        }
1855    }
1856    // endregion
1857
1858    // region: Counter tracking tests
1859
1860    #[test]
1861    fn scope_counter_starts_at_zero() {
1862        let scope = unsafe { ProtectScope::new() };
1863        assert_eq!(scope.count(), 0);
1864    }
1865
1866    // Note: The following tests require R to be initialized and would be
1867    // integration tests rather than unit tests:
1868    //
1869    // - Balance test: protect N, verify unprotect(N) on drop (gctorture)
1870    // - Nested scopes: verify drop order yields correct net unprotect
1871    // - Reprotect slot: verify set() many times keeps count at +1
1872    //
1873    // These should be tested in miniextendr-api/tests/gc_protect.rs with
1874    // embedded R.
1875    // endregion
1876
1877    // region: Protected<'a, T> tests
1878
1879    /// Verify `Protected::get()` returns the inner value.
1880    #[test]
1881    fn protected_get_returns_inner() {
1882        // from_trusted with null SEXP (safe for compile-time tests since we
1883        // never dereference the SEXP itself, only the already-constructed inner)
1884        let v = vec![1i32, 2, 3];
1885        let p = unsafe {
1886            Protected::<'static, Vec<i32>>::from_trusted(crate::SEXP(core::ptr::null_mut()), v)
1887        };
1888        assert_eq!(p.get(), &[1, 2, 3]);
1889    }
1890
1891    /// Verify `Deref<Target = T>` works (exercises the blanket impl).
1892    #[test]
1893    fn protected_deref_reaches_inner() {
1894        let v = vec![10i32, 20];
1895        let p = unsafe {
1896            Protected::<'static, Vec<i32>>::from_trusted(crate::SEXP(core::ptr::null_mut()), v)
1897        };
1898        // Deref should let us call Vec methods directly.
1899        assert_eq!(p.len(), 2);
1900    }
1901
1902    /// Verify `into_inner` moves the inner value out cleanly.
1903    #[test]
1904    fn protected_into_inner_moves_value() {
1905        let v = vec![42i32];
1906        let p = unsafe {
1907            Protected::<'static, Vec<i32>>::from_trusted(crate::SEXP(core::ptr::null_mut()), v)
1908        };
1909        let got = p.into_inner();
1910        assert_eq!(got, [42]);
1911    }
1912
1913    /// Verify `from_trusted` sets `_protect` to `None` (no extra protect push).
1914    #[test]
1915    fn protected_from_trusted_does_not_hold_protect() {
1916        let v: Vec<u8> = vec![];
1917        let p = unsafe {
1918            Protected::<'static, Vec<u8>>::from_trusted(crate::SEXP(core::ptr::null_mut()), v)
1919        };
1920        // This test uses `from_trusted` to avoid touching the protect stack;
1921        // `Protected::new` would require an initialized R runtime (see
1922        // `tests/gc_protect.rs` for that path). Verify the `_protect` field is
1923        // `None` indirectly via the `Debug` output.
1924        assert!(format!("{p:?}").contains("protected: false"));
1925    }
1926
1927    /// Smoke-test: `Protected::from_trusted` + `Deref` compiles for a scalar `T`.
1928    #[test]
1929    fn protected_from_trusted_compile_check() {
1930        let p = unsafe {
1931            Protected::<'static, i32>::from_trusted(crate::SEXP(core::ptr::null_mut()), 99)
1932        };
1933        assert_eq!(*p, 99);
1934    }
1935    // endregion
1936}
1937// endregion