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