pub struct ReprotectSlot<'a> {
idx: ProtectIndex,
cur: Cell<SEXP>,
_scope: PhantomData<&'a ProtectScope>,
_nosend: PhantomData<Rc<()>>,
}Expand description
A protected slot created with R_ProtectWithIndex and updated with R_Reprotect.
This allows updating a protected value in-place without growing the protection stack. Useful for loops that repeatedly allocate and update a value.
The slot is valid only while the creating ProtectScope is alive.
§When to Use ReprotectSlot
Use ReprotectSlot when you need to reassign a protected value multiple times:
| Pattern | Use | Why |
|---|---|---|
| Accumulator loop | ReprotectSlot | Repeatedly replace result without stack growth |
| Single allocation | ProtectScope::protect | Simpler, no reassignment needed |
| Child insertion | List::set_elt | Container handles child protection |
§Warning: RAII Assignment Pitfall
R’s PROTECT stack is LIFO. Rust’s RAII drop order can cause problems:
// WRONG - can unprotect the new value instead of the old!
let mut guard = OwnedProtect::new(old_value);
guard = OwnedProtect::new(new_value); // Old guard drops AFTER new is assignedReprotectSlot avoids this by using R_Reprotect which replaces in-place:
// CORRECT - always keeps exactly one slot protected
let slot = scope.protect_with_index(old_value);
slot.set(new_value); // R_Reprotect, no stack change§Examples
§Accumulator Pattern
unsafe fn sum_allocated_vectors(n: i32) -> SEXP {
let scope = ProtectScope::new();
// Initial allocation
let slot = scope.protect_with_index(Rf_allocVector(REALSXP, 10));
for i in 0..n {
// Each iteration allocates a new vector
let new_vec = compute_step(slot.get(), i);
slot.set(new_vec); // Replace without growing protect stack
}
slot.get()
}§Starting with Empty Slot
unsafe fn build_result(items: &[Input]) -> SEXP {
let scope = ProtectScope::new();
// Start with R_NilValue, replace with first real result
let slot = scope.protect_with_index(R_NilValue);
for (i, item) in items.iter().enumerate() {
let result = process_item(item, slot.get());
slot.set(result);
}
slot.get()
}§Multiple Slots
unsafe fn merge_sorted(a: SEXP, b: SEXP) -> SEXP {
let scope = ProtectScope::new();
let slot_a = scope.protect_with_index(a);
let slot_b = scope.protect_with_index(b);
let result = scope.protect_with_index(R_NilValue);
// Process both inputs, updating result
while !is_empty(slot_a.get()) && !is_empty(slot_b.get()) {
let merged = merge_next(slot_a.get(), slot_b.get());
result.set(merged);
// ... update slot_a and slot_b as needed
}
result.get()
}Fields§
§idx: ProtectIndex§cur: Cell<SEXP>§_scope: PhantomData<&'a ProtectScope>§_nosend: PhantomData<Rc<()>>Implementations§
Source§impl<'a> ReprotectSlot<'a>
impl<'a> ReprotectSlot<'a>
Sourcepub unsafe fn set(&self, x: SEXP) -> SEXP
pub unsafe fn set(&self, x: SEXP) -> SEXP
Replace the protected value in-place using R_Reprotect.
The new value x becomes protected in this slot, and the old value
is no longer protected (but may still be rooted elsewhere).
Returns the raw SEXP for convenience. Note that this SEXP is only
protected until the next call to set() on this slot - if you need
to hold multiple protected values simultaneously, use separate
protection slots or OwnedProtect.
§Safety
- Must be called from the R main thread
xmust be a valid SEXP
Sourcepub unsafe fn set_with<F>(&self, f: F) -> SEXP
pub unsafe fn set_with<F>(&self, f: F) -> SEXP
Allocate a new value via the closure and replace this slot’s value safely.
This method encodes the safe pattern for replacing a protected slot with a newly allocated value. It:
- Calls the closure
f()to allocate a new SEXP - Temporarily protects the new value (to close the GC gap)
- Calls
R_Reprotectto replace this slot’s value - Unprotects the temporary protection
This prevents the GC gap that would exist if you called f() and then
set() separately - during that window, the newly allocated value would
be unprotected.
§Safety
- Must be called from the R main thread
- The closure must return a valid SEXP
§Example
unsafe fn grow_list(scope: &ProtectScope, old_list: SEXP) -> SEXP {
let slot = scope.protect_with_index(old_list);
// Safely grow the list without GC gap
slot.set_with(|| {
let new_list = Rf_allocVector(VECSXP, new_size);
// copy elements from old_list to new_list...
new_list
});
slot.get()
}Sourcepub unsafe fn take(&self) -> SEXP
pub unsafe fn take(&self) -> SEXP
Take the current value and clear the slot to R_NilValue.
This provides Option::take-like semantics. The slot remains allocated
(protect stack depth unchanged), but now holds R_NilValue (immortal).
§Safety
- Must be called from the R main thread
- The returned SEXP is unprotected. If it needs to survive further allocations, you must protect it explicitly.
§Example
let slot = scope.protect_with_index(some_value);
// ... work with slot.get() ...
let old = slot.take(); // slot now holds R_NilValue
// old is unprotected - protect it if needed
let guard = OwnedProtect::new(old);Sourcepub unsafe fn replace(&self, x: SEXP) -> SEXP
pub unsafe fn replace(&self, x: SEXP) -> SEXP
Replace the slot’s value with x and return the old value.
This provides Option::replace-like semantics. The slot now protects
x, and the old value is returned unprotected.
§Safety
- Must be called from the R main thread
xmust be a valid SEXP- The returned SEXP is unprotected. If it needs to survive further allocations, you must protect it explicitly.
§Example
let slot = scope.protect_with_index(initial);
let old = slot.replace(new_value);
// old is unprotected, slot now protects new_value