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