pub struct Sidecar<T> {
pending: RefCell<Option<Pending>>,
_value: PhantomData<T>,
}Expand description
A sidecar field whose value R roots: it lives in the external pointer’s
protection list, typed as T on the Rust side.
Declare it as #[r_data] pub keys: Sidecar<Vec<i32>> on a
#[derive(ExternalPtr)] struct. The derive generates accessors as
associated functions of the struct, with the field’s visibility. They take
an owner handle, which keeps the pointer alive, not the struct:
#[r_data(...)] | Rust accessors |
|---|---|
ref | fn keys(ptr: &impl SidecarOwner<Self>) -> T |
mut | fn set_keys(ptr: &mut impl SidecarOwnerMut<Self>, value: T) |
ref, mut, or a bare #[r_data] | both |
Rust code sees no SEXPs, slot indices or other R plumbing: a plain
fn new() -> Self constructor fills the field with Sidecar::new, and
a method that needs the value takes a view as its receiver
(self: ExternalPtrRef<'_, Self> / mut self: ExternalPtrMut<'_, Self>)
and calls Self::keys(&self) / Self::set_keys(&mut self, v); other Rust
code writes Engine::keys(&ptr). The R side keeps its own accessors whatever the
option: for a pub field, Type_get_keys(x) / Type_set_keys(x, value),
an R6 active binding (r6(r_data_accessors)), an S7 property
(s7(r_data_accessors)), or x$keys / x$keys <- value on an S3, S4 or
env class (s3(r_data_accessors) & co.). The R setter validates the new
value with TryFromSexp::<T> and stores the value R gave.
#[derive(ExternalPtr)]
#[externalptr(r6)]
pub struct Engine {
n: i32,
#[r_data] _r: RSidecar,
#[r_data(ref, mut)] pub keys: Sidecar<Vec<i32>>,
#[r_data(ref)] pub labels: Sidecar<Vec<String>>,
#[r_data(ref, mut)] cache: Sidecar<Option<List>>,
}
#[miniextendr(r6(r_data_accessors))]
impl Engine {
pub fn new(n: i32) -> Self {
Engine {
n,
_r: RSidecar,
keys: Sidecar::new((1..=n).collect()),
labels: Sidecar::new(vec![]),
cache: Sidecar::default(),
}
}
pub fn key_count(self: ExternalPtrRef<'_, Self>) -> usize { Self::keys(&self).len() }
pub fn remember(mut self: ExternalPtrMut<'_, Self>, value: List) {
Self::set_cache(&mut self, Some(value))
}
}
// From any other Rust code holding the handle:
let mut ptr = ExternalPtr::new(Engine::new(3));
Engine::set_keys(&mut ptr, vec![9]);
assert_eq!(Engine::keys(&ptr), vec![9]);The accessors take super::ExternalPtrRef / super::ExternalPtrMut
views (receivers, arguments, borrow() / borrow_mut()) or an
ExternalPtr, which they borrow for the call: shared for the getter,
exclusive for the setter. A plain &self receiver has no route back to
an R owner.
§Values
T: IntoR + TryFromSexp, withT::Error: Displayfor the getter’s panic message.- A getter converts the slot with
TryFromSexp, so a converting type (Vec<i32>,String) copies on every read. A conversion failure is a panic naming the field and the type; the framework turns it into an R error. Sidecar<SEXP>,Sidecar<List>andSidecar<DataFrame>keep an arbitrary R object unconverted. The getter returns a view that the slot roots only until the next write to that field; don’t hold it across one.Sidecar::newandSidecar::replaceconvert their value withIntoRat once and root the R object it gives until a slot holds it (#2216). A freshSEXPor list passed to them is safe from that moment; it needs its own protection only before the call.
§Lifecycle
- Pending.
Sidecar::new(v)andDefault(Sidecar::new(T::default())) convertvto its R object and hold it as a pending value in the struct, rooted in a main-thread pool until a slot holds it or the last copy drops. That is the only state aSidecarhas: before the struct is wrapped, after*ptr = Engine::new(..), afterExternalPtr::into_inner. They run on R’s main thread, or on the managed worker, which hands the conversion to the main thread asExternalPtr::newdoes; any other thread panics. Like an owningExternalPtr, the last copy of a pending value must be dropped on R’s main thread or its managed worker. - Flushed when wrapped.
ExternalPtr::newand the trait ABI’smx_wrapmove every pending value into the pointer’s protection list. From then on the value lives in R only, and the field is empty. - Flushed on every access to the field. The handle getter and setter,
and the R getter and setter, first move that field’s pending value into
its slot (the setters drop it: the write supersedes it). So
*ptr.borrow_mut() = Engine::new(..)orptr.borrow_mut().keys = Sidecar::new(v)takes effect at the next access from either side. AsaveRDS()before any access writes the old slot value. - Loaded when the value leaves its pointer.
ExternalPtr::into_innerandinto_rawread each slot of the pointer being consumed back into its field as a pending value (unless one is pending already), rooted as a new one is, so wrapping the value again keeps the values. - A
self -> Selfmethod leaves the values in the pointer across the call; the write-back flushes only what the method set withSidecar::new.Self { n: .., ..self }keeps the values,Self::new(..)replaces them. - Clone.
Sidecar::cloneshares the pending R object and its root, which is none once the struct is wrapped.ExternalPtr::cloneandclone_fromcopy the slot values as R objects, so the two pointers share them until one side writes the field: every write replaces the slot, and R copies a shared value before modifying it in place. A struct cloned outside its handle carries no sidecar values; clone the handle. *ptr = newandmem::swap(&mut *a, &mut *b)are plain Rust moves. The sidecar values stay in each pointer’s protection list; only pending values travel with the struct.- After
readRDS()there is no Rust value. The standalone R accessors (Type_get_f()/Type_set_f()), the R6 active bindings and the S7 properties read and write the protection list directly, checked by the type ID stored with it. The$/[[/$<-/[[<-field methods ofs3(r_data_accessors)& co. refuse the restored pointer instead, with the classed restored-object errors: they passrequire_liveto the same accessor (#1891).
Use the accessors on R’s main thread only. Off the main thread an accessor panics with a clear message before it touches R.
§A save names the values by their R names
A saved object (saveRDS(), serialize()) stores one slot per Sidecar
field, named after the field’s R name: the #[r_data(name = "..")] value,
else the Rust field name (#2187, #2215). After a reload, the standalone R
accessors, the R6 bindings, the S7 properties and a rebuild hook
(#[externalptr(rebuild = path)], through super::Saved) find each
field’s slot by that name. So the R name is the save format:
- Reordering, adding or removing
Sidecarfields keeps earlier saves readable. A field the save has no slot for raises the classedRESTORED_MISSING_SLOT_CLASSerror (or reads asNonethrough a hook’sSelf::try_<field>(saved)). - Renaming the Rust field keeps them readable too, as long as the R name
stays: a field without
name = ..keeps its old ident as#[r_data(name = "old")]. - Renaming the R name orphans the field’s slot in every earlier save: the field reads as one the save lacks.
- Retyping a field keeps its name, so an earlier save’s slot is read as the new type; a getter that can’t convert it panics naming the field.
A save from another version of the crate (the version in its
Cargo.toml, which the stored type ID records) is refused with the classed
miniextendr_restored_other_version error (#1872), unless the type
rebuilds it (rebuild = path, any_version).
§Methods that need sidecar values take a view
A &self / &mut self method sees the struct, which holds no reference
to its pointer, so it can’t reach the values. Take a view instead:
self: ExternalPtrRef<'_, Self> to read, mut self: ExternalPtrMut<'_, Self>
to write (Deref still gives the struct fields). The same holds for a
struct moved out of its pointer through &mut (mem::replace,
mem::take): it carries nothing to follow, so nothing can read a freed R
object (#1856). A trait method takes its owner as
self: ExternalPtrRef<'_, Self> or self: ExternalPtrMut<'_, Self>
(ExternalPtrRef), over an ExternalPtr or a
trait-ABI object, and passes it to the same accessors. A &self trait
method, such as one of a standard trait with a fixed signature, and the
high-level ALTREP data traits receive only the struct (#1880).
ALTREP derives also accept #[r_data] Sidecar<T> fields: with
#[altrep(manual, no_lowlevel)], low-level callbacks recover the handle
through crate::altrep_data1_as_unchecked and call the associated
accessors. Those derives emit no R class field wrappers.
A plain SEXP field under #[r_data] is a compile error: nothing would
root it. Use Sidecar<SEXP>.
Fields§
§pending: RefCell<Option<Pending>>The value the field holds while it is not in a pointer’s slot.
_value: PhantomData<T>The field’s type: its accessors convert the slot to it, and the
struct’s auto traits follow it as if it held a T.
Implementations§
Source§impl<T: IntoR + 'static> Sidecar<T>
impl<T: IntoR + 'static> Sidecar<T>
Sourcepub fn new(value: T) -> Self
pub fn new(value: T) -> Self
A sidecar field holding value, pending until the struct is wrapped
in an ExternalPtr.
Converts value with IntoR and roots the R object until a slot
holds it, so a fresh R value is safe from here on.
§Panics
Off R’s main thread and its managed worker, before converting; or when the conversion fails.
Sourcepub fn replace(value: T) -> Self
pub fn replace(value: T) -> Self
A sidecar field holding value, which replaces the slot a save kept
when a rebuild hook (#[externalptr(rebuild = path)]) returns it.
A rebuilt object keeps each slot its save has: the hook’s
Sidecar::new values are placeholders for those fields, and fill only
the fields the save lacks. A hook that recomputes a field’s value, and
wants the object to hold the new value rather than the saved one,
returns it as Sidecar::replace(value). Anywhere else it is
Sidecar::new(value). Converts and roots value as
Sidecar::new does, with the same panics.
Source§impl<T> Sidecar<T>
impl<T> Sidecar<T>
Sourcefn replaces_saved(&self) -> bool
fn replaces_saved(&self) -> bool
Whether the pending value was made by Sidecar::replace.
Sourcefn discard_pending(&self)
fn discard_pending(&self)
Drops a pending value, which a write to the slot supersedes.
Sourcefn flush_into(&self, prot: SEXP, index: usize)
fn flush_into(&self, prot: SEXP, index: usize)
Moves a pending value into slot index of prot, if there is one.
On R’s main thread; set_vector_elt allocates nothing.
Trait Implementations§
Source§impl<T> SidecarField for Sidecar<T>
impl<T> SidecarField for Sidecar<T>
Source§fn __mx_flush(&self, prot: SEXP, index: usize)
fn __mx_flush(&self, prot: SEXP, index: usize)
index of prot.Source§fn __mx_load(&self, prot: SEXP, index: usize)
fn __mx_load(&self, prot: SEXP, index: usize)
index of prot back into the field as its pending value,
unless one is pending already.Source§fn __mx_discard_pending(&self)
fn __mx_discard_pending(&self)
Source§fn __mx_replaces_saved(&self) -> bool
fn __mx_replaces_saved(&self) -> bool
Sidecar::replace.Auto Trait Implementations§
impl<T> !Freeze for Sidecar<T>
impl<T> !RefUnwindSafe for Sidecar<T>
impl<T> !Sync for Sidecar<T>
impl<T> Send for Sidecar<T>where
PhantomData<T>: Send,
impl<T> Unpin for Sidecar<T>where
PhantomData<T>: Unpin,
impl<T> UnsafeUnpin for Sidecar<T>where
PhantomData<T>: UnsafeUnpin,
impl<T> UnwindSafe for Sidecar<T>where
PhantomData<T>: UnwindSafe,
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> ConsumingFallible for Twhere
T: Clone,
Source§impl<T> RDebug for Twhere
T: Debug,
impl<T> RDebug for Twhere
T: Debug,
Source§impl<T> SizeHint for Twhere
T: ?Sized,
impl<T> SizeHint for Twhere
T: ?Sized,
Source§default fn lower_bound(&self) -> usize
default fn lower_bound(&self) -> usize
core_io_internals)[u8; 12] could return any value between 0 and
12 inclusively as a correct implementation. Read moreSource§impl<T> SizedTypeProperties for T
impl<T> SizedTypeProperties for T
Source§#[doc(hidden)]const SIZE: usize = _
#[doc(hidden)]const SIZE: usize = _
sized_type_properties)Source§#[doc(hidden)]const ALIGN: usize = _
#[doc(hidden)]const ALIGN: usize = _
sized_type_properties)Source§#[doc(hidden)]const ALIGNMENT: Alignment = _
#[doc(hidden)]const ALIGNMENT: Alignment = _
ptr_alignment_type)Source§#[doc(hidden)]const IS_ZST: bool = _
#[doc(hidden)]const IS_ZST: bool = _
sized_type_properties)Source§#[doc(hidden)]const LAYOUT: Layout = _
#[doc(hidden)]const LAYOUT: Layout = _
sized_type_properties)Source§#[doc(hidden)]const MAX_SLICE_LEN: usize = _
#[doc(hidden)]const MAX_SLICE_LEN: usize = _
sized_type_properties)[Self]. Read moreLayout§
Note: Most layout information is completely unstable and may even differ between compilations. The only exception is types with certain repr(...) attributes. Please see the Rust Reference's “Type Layout” chapter for details on type layout guarantees.
Size: 32 bytes