Skip to main content

ReprotectSlot

Struct ReprotectSlot 

Source
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:

PatternUseWhy
Accumulator loopReprotectSlotRepeatedly replace result without stack growth
Single allocationProtectScope::protectSimpler, no reassignment needed
Child insertionList::set_eltContainer 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 assigned

ReprotectSlot 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>

Source

pub fn get(&self) -> SEXP

Get the currently protected SEXP.

Source

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
  • x must be a valid SEXP
Source

pub unsafe fn set_with<F>(&self, f: F) -> SEXP
where F: FnOnce() -> 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:

  1. Calls the closure f() to allocate a new SEXP
  2. Temporarily protects the new value (to close the GC gap)
  3. Calls R_Reprotect to replace this slot’s value
  4. 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()
}
Source

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);
Source

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
  • x must 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
Source

pub unsafe fn clear(&self)

Clear the slot by setting it to R_NilValue.

The slot remains allocated (protect stack depth unchanged), but releases its reference to the previous value. The previous value may still be rooted elsewhere.

§Safety

Must be called from the R main thread.

Source

pub unsafe fn is_nil(&self) -> bool

Check if the slot is currently cleared (holds R_NilValue).

§Safety

Must be called from the R main thread (accesses R’s R_NilValue).

Auto Trait Implementations§

§

impl<'a> !Freeze for ReprotectSlot<'a>

§

impl<'a> !RefUnwindSafe for ReprotectSlot<'a>

§

impl<'a> !Send for ReprotectSlot<'a>

§

impl<'a> !Sync for ReprotectSlot<'a>

§

impl<'a> Unpin for ReprotectSlot<'a>

§

impl<'a> UnsafeUnpin for ReprotectSlot<'a>

§

impl<'a> !UnwindSafe for ReprotectSlot<'a>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.