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