Skip to main content

Module gc_protect

Module gc_protect 

Source
Expand description

GC protection tools built on R’s PROTECT stack.

This module provides RAII wrappers around R’s GC protection primitives.

§Submodules

ModuleContents
tlsThread-local convenience API — tls::protect(x) without passing &ProtectScope

§Core Types

  • ProtectScope — RAII scope that calls UNPROTECT(n) on drop
  • OwnedProtect — single-value RAII protect/unprotect
  • Root — lifetime-tied handle to a protected SEXP
  • ReprotectSlotPROTECT_WITH_INDEX + REPROTECT for mutable slots

§Protection Strategies in miniextendr

miniextendr provides three complementary protection mechanisms for different scenarios:

StrategyModuleLifetimeRelease OrderUse Case
PROTECT stackgc_protectWithin .CallLIFO (stack)Temporary allocations
Preserve listpreserveAcross .CallsAny orderLong-lived R objects
R ownershipExternalPtrUntil R GCsR decidesRust data owned by R

§When to Use Each

Use gc_protect (this module) when:

  • You allocate R objects during a .Call and need them protected until return
  • You want RAII-based automatic balancing of PROTECT/UNPROTECT
  • Protection is short-lived (within a single function)

Use preserve when:

  • Objects must survive across multiple .Call invocations
  • You need to release protections in arbitrary order
  • Example: RAllocator backing memory

Use ExternalPtr when:

  • You want R to own a Rust value
  • The Rust value should be dropped when R garbage collects the pointer
  • You’re exposing Rust structs to R code

§Visual Overview

┌─────────────────────────────────────────────────────────────────┐
│  .Call("my_func", x)                                            │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │  ProtectScope::new()                                     │   │
│  │  ├── protect(Rf_allocVector(...))  // temp allocation    │   │
│  │  ├── protect(Rf_allocVector(...))  // another temp       │   │
│  │  └── UNPROTECT(n) on scope drop                          │   │
│  └──────────────────────────────────────────────────────────┘   │
│                          ↓ return SEXP                          │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│  preserve (objects surviving across .Calls)                     │
│  ├── preserve::insert(sexp)   // add to linked list             │
│  ├── ... multiple .Calls ...  // object stays protected         │
│  └── preserve::release(cell)  // remove when done               │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│  ExternalPtr<MyStruct> (R owns Rust data)                       │
│  ├── Construction: temporary Rf_protect                         │
│  ├── Return to R → R owns the EXTPTRSXP                         │
│  └── R GC → finalizer runs → Rust Drop executes                 │
└─────────────────────────────────────────────────────────────────┘

§Types in This Module

This module provides RAII wrappers around R’s GC protection primitives:

TypePurpose
ProtectScopeBatch protection with automatic UNPROTECT(n) on drop
Root<'scope>Lightweight handle tied to a scope’s lifetime
OwnedProtectSingle-value RAII guard for simple cases
ReprotectSlot<'scope>Protected slot supporting replace-under-protection

§Design Principles

  • ProtectScope owns the responsibility of calling UNPROTECT(n)
  • Root<'a> is a move-friendly, non-dropping handle whose lifetime ties to the scope
  • ReprotectSlot<'a> supports replace-under-protection via PROTECT_WITH_INDEX/REPROTECT

§Safety Model

These tools are unsafe to create because they require:

  1. Running on the R main thread - R’s API is not thread-safe
  2. No panics across FFI - Rust panics must not unwind across C boundary
  3. Understanding R errors - If R raises an error (longjmp), Rust destructors will not run, so scope-based unprotection will leak

For cleanup that survives R errors, use R_UnwindProtect boundaries in your .Call trampoline (see unwind_protect).

§Example

use miniextendr_api::gc_protect::ProtectScope;
use miniextendr_api::ffi::SEXP;

unsafe fn process_vectors(x: SEXP, y: SEXP) -> SEXP {
    let scope = ProtectScope::new();

    // Protect multiple values
    let x = scope.protect(x);
    let y = scope.protect(y);

    // Work with protected values...
    let result = scope.protect(some_r_function(x.get(), y.get()));

    result.into_raw()
} // UNPROTECT(3) called automatically

§Container Insertion Patterns

When building containers (lists, character vectors), children need protection between allocation and insertion:

// WRONG - child unprotected between allocation and SET_VECTOR_ELT
let child = Rf_allocVector(REALSXP, 10);  // unprotected!
list.set_vector_elt(0, child);           // GC could occur before this!

// CORRECT - use safe insertion methods
let list = List::from_raw(scope.alloc_vecsxp(n).into_raw());
for i in 0..n {
    let child = Rf_allocVector(REALSXP, 10);
    list.set_elt(i, child);  // protects child during insertion
}

// EFFICIENT - use ListBuilder with scope
let builder = ListBuilder::new(&scope, n);
for i in 0..n {
    let child = scope.alloc_real(10).into_raw();
    builder.set(i, child);  // child already protected by scope
}

See List::set_elt, ListBuilder, and StrVec::set_str for safe container APIs.

§Reassignment with ReprotectSlot

Use ReprotectSlot when you need to reassign a protected value multiple times without growing the protection stack:

let slot = scope.protect_with_index(initial_value);
for item in items {
    let new_value = process(slot.get(), item);
    slot.set(new_value);  // R_Reprotect, stack count unchanged
}

This avoids the LIFO drop-order pitfall of reassigning OwnedProtect guards.

Modules§

tls
TLS-backed convenience API for GC protection.

Structs§

OwnedProtect
A single-object RAII guard: PROTECT on create, UNPROTECT(1) on drop.
ProtectScope
A scope that automatically balances UNPROTECT(n) on drop.
ReprotectSlot
A protected slot created with R_ProtectWithIndex and updated with R_Reprotect.
Root
A rooted SEXP tied to the lifetime of a ProtectScope.
WorkerUnprotectGuard
A Send-safe guard that calls Rf_unprotect(n) on drop via with_r_thread.

Traits§

Protector
A scope-like GC protection backend.

Type Aliases§

NoSendSync 🔒
Enforces !Send + !Sync (R API is not thread-safe).
ProtectIndex
R’s PROTECT_INDEX type (just c_int under the hood).