Skip to main content

miniextendr_api/
sexp.rs

1//! The `SEXP` newtype and its inherent methods.
2//!
3//! `SEXP` is the central pointer type for R values. It's a thin newtype over
4//! `*mut SEXPREC` that implements `Send`/`Sync` for cross-thread plumbing
5//! (the pointed-to data must still only be touched on R's main thread).
6//!
7//! For the broader vocabulary of safe SEXP accessors, see [`crate::sexp_ext::SexpExt`].
8
9use crate::sexp_types::{CE_UTF8, R_xlen_t, Rcomplex, SEXPTYPE};
10// Pull in the extern bindings and module-level fns that the inherent methods
11// dispatch to. The type-level re-exports in `sys` (SEXP, SEXPREC, SEXPTYPE,
12// etc.) shadow but are identical to what we're defining locally — that's
13// fine because they're the same items.
14use crate::sys::{
15    R_BaseNamespace, R_BlankString, R_ClassSymbol, R_DimNamesSymbol, R_DimSymbol, R_LevelsSymbol,
16    R_MissingArg, R_NaString, R_NamesSymbol, R_NilValue, R_TspSymbol, R_altrep_data1,
17    R_altrep_data1_unchecked, R_altrep_data2, R_altrep_data2_unchecked, R_set_altrep_data1,
18    R_set_altrep_data2, R_set_altrep_data2_unchecked, Rf_ScalarComplex, Rf_ScalarComplex_unchecked,
19    Rf_ScalarInteger, Rf_ScalarInteger_unchecked, Rf_ScalarLogical, Rf_ScalarLogical_unchecked,
20    Rf_ScalarRaw, Rf_ScalarRaw_unchecked, Rf_ScalarReal, Rf_ScalarReal_unchecked, Rf_ScalarString,
21    Rf_ScalarString_unchecked, Rf_allocVector, Rf_installChar, Rf_mkCharLenCE,
22};
23
24#[repr(transparent)]
25#[derive(#[automatically_derived]
impl ::core::fmt::Debug for SEXPREC {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_tuple_field1_finish(f, "SEXPREC",
            &&self.0)
    }
}Debug)]
26/// Opaque underlying S-expression header type.
27pub struct SEXPREC(::std::os::raw::c_void);
28
29/// R's pointer type for S-expressions.
30///
31/// This is a newtype wrapper around `*mut SEXPREC` that implements Send and Sync.
32/// SEXP is just a handle (pointer) - the actual data it points to is managed by R's
33/// garbage collector and should only be accessed on R's main thread.
34///
35/// # Safety
36///
37/// While SEXP is Send+Sync (allowing it to be passed between threads), the data
38/// it points to must only be accessed on R's main thread. The miniextendr runtime
39/// enforces this through checked FFI wrappers and `with_r_thread` routing when
40/// worker dispatch is selected.
41///
42/// # Equality Semantics
43///
44/// IMPORTANT: The derived `PartialEq` compares **pointer equality**, not semantic equality.
45/// For proper R semantics (comparing object contents), use `R_compute_identical`.
46///
47/// ```ignore
48/// // Pointer equality (fast, often wrong for R semantics)
49/// if sexp1 == sexp2 { ... }  // Only true if same pointer
50///
51/// // Semantic equality (correct R semantics)
52/// if R_compute_identical(sexp1, sexp2, 16) != 0 { ... }
53/// ```
54///
55/// **Hash trait removed**: SEXP no longer implements `Hash` because proper hashing
56/// would require deep content inspection via `R_compute_identical`, which is too
57/// expensive for general use. If you need SEXP as a HashMap key, use pointer identity:
58///
59/// ```ignore
60/// // Store by pointer identity (common pattern for R symbol lookups)
61/// let mut map: HashMap<*mut SEXPREC, Value> = HashMap::new();
62/// map.insert(sexp.as_ptr(), value);
63/// ```
64#[repr(transparent)]
65#[derive(#[automatically_derived]
impl ::core::fmt::Debug for SEXP {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_tuple_field1_finish(f, "SEXP", &&self.0)
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for SEXP { }
#[automatically_derived]
impl ::core::clone::Clone for SEXP {
    #[inline]
    fn clone(&self) -> Self {
        let _: ::core::clone::AssertParamIsClone<*mut SEXPREC>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for SEXP { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for SEXP { }
#[automatically_derived]
impl ::core::cmp::PartialEq for SEXP {
    #[inline]
    fn eq(&self, other: &Self) -> bool { self.0 == other.0 }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for SEXP {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<*mut SEXPREC>;
    }
}Eq)]
66pub struct SEXP(pub *mut SEXPREC);
67
68// SAFETY: SEXP is just a pointer (memory address). Passing the address between
69// threads is safe. The actual data access is protected by miniextendr's runtime
70// which ensures R API calls happen on the main thread.
71unsafe impl Send for SEXP {}
72unsafe impl Sync for SEXP {}
73
74impl SEXP {
75    /// Create a C null pointer SEXP (0x0).
76    ///
77    /// This is **not** R's `NULL` value (`R_NilValue`). R's `NULL` is a real
78    /// heap-allocated singleton; a C null pointer is just address zero. Passing
79    /// `SEXP::null()` where R expects `R_NilValue` will corrupt R's GC state
80    /// and likely segfault.
81    ///
82    /// Use [`SEXP::nil()`] for R's `NULL`. Only use `null()` for low-level
83    /// pointer initialization, ALTREP Sum/Min/Max "can't compute" returns
84    /// (R checks `!= NULL`, not `!= R_NilValue`), or comparison against
85    /// uninitialized pointers.
86    ///
87    /// See also: [`SEXP::nil()`], [`SEXP::is_null_ptr()`], [`SEXP::is_nil()`]
88    #[inline]
89    pub const fn null() -> Self {
90        Self(std::ptr::null_mut())
91    }
92
93    /// Return R's `NULL` singleton (`R_NilValue`).
94    ///
95    /// This is **not** a C null pointer — it points to R's actual nil object
96    /// on the heap. Use this for `.Call()` return values, SEXP arguments to
97    /// R API functions, and any slot in R data structures.
98    ///
99    /// See also: [`SEXP::null()`], [`SEXP::is_nil()`], [`SEXP::is_null_ptr()`]
100    #[inline]
101    pub fn nil() -> Self {
102        unsafe { R_NilValue }
103    }
104
105    /// Is this SEXP a C null pointer (address `0x0`)?
106    ///
107    /// This answers a pointer question, not an R question. An SEXP that R
108    /// hands you (a `.Call()` argument, a list element, an attribute) is never
109    /// a C null pointer: R's `NULL` is the `R_NilValue` singleton, a real
110    /// object at a non-zero address. A C null pointer only shows up where C
111    /// code uses address zero for "nothing here": an uninitialised static or
112    /// field ([`SEXP::null()`]), an ALTREP `data1`/`data2` slot that was never
113    /// set, or an FFI function that returns `NULL` on failure.
114    ///
115    /// To ask "is this R's `NULL`?", use [`SEXP::is_nil()`].
116    ///
117    /// ```
118    /// use miniextendr_api::SEXP;
119    ///
120    /// let unset = SEXP::null();
121    /// assert!(unset.is_null_ptr());
122    /// ```
123    ///
124    /// There is no `SEXP::is_null()`: the name read like a test for R's
125    /// `NULL`, so it is gone on purpose.
126    ///
127    /// ```compile_fail
128    /// use miniextendr_api::SEXP;
129    ///
130    /// let unset = SEXP::null();
131    /// assert!(unset.is_null());
132    /// ```
133    ///
134    /// See also: [`SEXP::is_nil()`], [`SEXP::is_null_or_nil()`], [`SEXP::null()`]
135    #[inline]
136    #[must_use]
137    pub const fn is_null_ptr(self) -> bool {
138        self.0.is_null()
139    }
140
141    /// Is this SEXP R's `NULL` (`R_NilValue`)?
142    ///
143    /// This is the check for an R value: an optional argument that defaults
144    /// to `NULL`, an absent attribute, an empty list slot. It compares the
145    /// address against the `R_NilValue` singleton and never dereferences
146    /// `self`, so it is safe on a stale SEXP. A C null pointer is not R's
147    /// `NULL`; for that, use [`SEXP::is_null_ptr()`].
148    ///
149    /// ```
150    /// use miniextendr_api::SEXP;
151    ///
152    /// // `x` is an argument whose R default is `NULL`: `f()` and `f(NULL)`
153    /// // both arrive as `R_NilValue`.
154    /// fn given(x: SEXP) -> bool {
155    ///     !x.is_nil()
156    /// }
157    /// # let _ = given;
158    /// ```
159    ///
160    /// See also: [`SEXP::nil()`], [`SEXP::is_null_ptr()`], [`SEXP::is_null_or_nil()`]
161    #[inline]
162    #[must_use]
163    pub fn is_nil(self) -> bool {
164        // Pointer comparison, not a type check: reading TYPEOF would crash on
165        // a freed SEXP during cleanup.
166        std::ptr::addr_eq(self.0, unsafe { R_NilValue.0 })
167    }
168
169    /// Is this SEXP either a C null pointer or R's `NULL`?
170    ///
171    /// For a slot that can hold either kind of "nothing": unset (address
172    /// zero) or set to `R_NilValue`, such as an external pointer's protected
173    /// value. For an R value alone, [`SEXP::is_nil()`] is the right check.
174    ///
175    /// ```
176    /// use miniextendr_api::SEXP;
177    ///
178    /// // A slot that was never set counts as empty.
179    /// assert!(SEXP::null().is_null_or_nil());
180    /// ```
181    ///
182    /// See also: [`SEXP::is_nil()`], [`SEXP::is_null_ptr()`]
183    #[inline]
184    #[must_use]
185    pub fn is_null_or_nil(self) -> bool {
186        self.is_null_ptr() || self.is_nil()
187    }
188
189    /// Get the raw pointer.
190    #[inline]
191    pub const fn as_ptr(self) -> *mut SEXPREC {
192        self.0
193    }
194
195    /// Create from a raw pointer.
196    #[inline]
197    pub const fn from_ptr(ptr: *mut SEXPREC) -> Self {
198        Self(ptr)
199    }
200
201    // region: String construction
202
203    /// Create a CHARSXP from a Rust `&str` (UTF-8).
204    #[inline]
205    pub fn charsxp(s: &str) -> SEXP {
206        let len: i32 = s.len().try_into().expect("string exceeds i32::MAX bytes");
207        unsafe { Rf_mkCharLenCE(s.as_ptr().cast(), len, CE_UTF8) }
208    }
209
210    /// R's `NA_character_` singleton.
211    #[inline]
212    pub fn na_string() -> SEXP {
213        unsafe { R_NaString }
214    }
215
216    /// R's empty string `""` singleton.
217    #[inline]
218    pub fn blank_string() -> SEXP {
219        unsafe { R_BlankString }
220    }
221
222    /// Create an R symbol (SYMSXP) from a CHARSXP.
223    ///
224    /// Equivalent to `Rf_installChar(charsxp)`. The symbol is interned
225    /// in R's global symbol table and never garbage collected.
226    #[inline]
227    pub fn install_char(charsxp: SEXP) -> SEXP {
228        unsafe { Rf_installChar(charsxp) }
229    }
230
231    /// Create an R symbol (SYMSXP) from a Rust `&str`.
232    ///
233    /// Combines `SEXP::charsxp()` + `Rf_installChar` into one call.
234    /// The symbol is interned and never garbage collected.
235    #[inline]
236    pub fn symbol(name: &str) -> SEXP {
237        Self::install_char(Self::charsxp(name))
238    }
239
240    // endregion
241
242    // region: Scalar construction
243
244    /// Create a length-1 integer vector.
245    #[inline]
246    pub fn scalar_integer(x: i32) -> SEXP {
247        unsafe { Rf_ScalarInteger(x) }
248    }
249
250    /// Create a length-1 real vector.
251    #[inline]
252    pub fn scalar_real(x: f64) -> SEXP {
253        unsafe { Rf_ScalarReal(x) }
254    }
255
256    /// Create a length-1 logical vector.
257    ///
258    /// Produces only `TRUE` or `FALSE`; a `bool` cannot represent R's `NA`.
259    /// For an NA logical, use [`scalar_logical_raw`](Self::scalar_logical_raw)
260    /// with `NA_LOGICAL` (`i32::MIN`).
261    #[inline]
262    pub fn scalar_logical(x: bool) -> SEXP {
263        unsafe { Rf_ScalarLogical(if x { 1 } else { 0 }) }
264    }
265
266    /// Create a length-1 logical vector from raw i32 (0=FALSE, 1=TRUE, NA_LOGICAL=NA).
267    #[inline]
268    /// Accepts 0 (FALSE), 1 (TRUE), or `NA_LOGICAL` (`i32::MIN`) for NA.
269    /// Prefer [`scalar_logical`](Self::scalar_logical) for non-NA values.
270    pub fn scalar_logical_raw(x: i32) -> SEXP {
271        unsafe { Rf_ScalarLogical(x) }
272    }
273
274    /// Create a length-1 raw vector.
275    #[inline]
276    pub fn scalar_raw(x: u8) -> SEXP {
277        unsafe { Rf_ScalarRaw(x) }
278    }
279
280    /// Create a length-1 complex vector.
281    #[inline]
282    pub fn scalar_complex(x: Rcomplex) -> SEXP {
283        unsafe { Rf_ScalarComplex(x) }
284    }
285
286    /// Create a length-1 character vector from a CHARSXP.
287    #[inline]
288    pub fn scalar_string(charsxp: SEXP) -> SEXP {
289        unsafe { Rf_ScalarString(charsxp) }
290    }
291
292    /// Create a length-1 character vector from a Rust `&str`.
293    #[inline]
294    pub fn scalar_string_from_str(s: &str) -> SEXP {
295        Self::scalar_string(Self::charsxp(s))
296    }
297
298    // Unchecked scalar constructors — skip the `with_r_thread` check.
299    // Use only inside ALTREP callbacks, `with_r_unwind_protect`, or `with_r_thread` blocks
300    // where the R-thread invariant is already established (see `#[r_ffi_checked]` docs).
301
302    /// Create a length-1 integer vector (unchecked — no thread routing).
303    ///
304    /// # Safety
305    ///
306    /// Must be called from the R main thread.
307    #[inline]
308    pub unsafe fn scalar_integer_unchecked(x: i32) -> SEXP {
309        unsafe { Rf_ScalarInteger_unchecked(x) }
310    }
311
312    /// Create a length-1 real vector (unchecked — no thread routing).
313    ///
314    /// # Safety
315    ///
316    /// Must be called from the R main thread.
317    #[inline]
318    pub unsafe fn scalar_real_unchecked(x: f64) -> SEXP {
319        unsafe { Rf_ScalarReal_unchecked(x) }
320    }
321
322    /// Create a length-1 logical vector from raw i32 (unchecked — no thread routing).
323    ///
324    /// Accepts 0 (FALSE), 1 (TRUE), or `NA_LOGICAL` (`i32::MIN`) for NA.
325    ///
326    /// # Safety
327    ///
328    /// Must be called from the R main thread.
329    #[inline]
330    pub unsafe fn scalar_logical_raw_unchecked(x: i32) -> SEXP {
331        unsafe { Rf_ScalarLogical_unchecked(x) }
332    }
333
334    /// Create a length-1 raw vector (unchecked — no thread routing).
335    ///
336    /// # Safety
337    ///
338    /// Must be called from the R main thread.
339    #[inline]
340    pub unsafe fn scalar_raw_unchecked(x: u8) -> SEXP {
341        unsafe { Rf_ScalarRaw_unchecked(x) }
342    }
343
344    /// Create a length-1 complex vector (unchecked — no thread routing).
345    ///
346    /// # Safety
347    ///
348    /// Must be called from the R main thread.
349    #[inline]
350    pub unsafe fn scalar_complex_unchecked(x: Rcomplex) -> SEXP {
351        unsafe { Rf_ScalarComplex_unchecked(x) }
352    }
353
354    /// Create a length-1 character vector from a CHARSXP (unchecked — no thread routing).
355    ///
356    /// # Safety
357    ///
358    /// Must be called from the R main thread.
359    #[inline]
360    pub unsafe fn scalar_string_unchecked(charsxp: SEXP) -> SEXP {
361        unsafe { Rf_ScalarString_unchecked(charsxp) }
362    }
363
364    // endregion
365
366    // region: Vector allocation
367
368    /// Allocate a fresh R vector of the given type and length.
369    ///
370    /// Direct wrapper over `Rf_allocVector`. For typed allocations, prefer
371    /// helpers like [`SEXP::alloc_list`], [`SEXP::alloc_strsxp`], or wrap the
372    /// result in [`OwnedProtect`](crate::gc_protect::OwnedProtect) immediately
373    /// — the returned SEXP is unprotected.
374    ///
375    /// # Safety
376    ///
377    /// Must be called from the R main thread. The returned SEXP is unprotected;
378    /// any subsequent allocation may collect it.
379    #[inline]
380    pub unsafe fn alloc(ty: SEXPTYPE, n: R_xlen_t) -> SEXP {
381        unsafe { Rf_allocVector(ty, n) }
382    }
383
384    /// Allocate an R list (VECSXP) of length `n`. Unprotected.
385    ///
386    /// Equivalent to `Rf_allocVector(VECSXP, n)`. Elements are initialised to `R_NilValue`.
387    ///
388    /// # Safety
389    ///
390    /// Must be called from the R main thread. The returned SEXP is unprotected —
391    /// wrap it in [`OwnedProtect`](crate::gc_protect::OwnedProtect) before any
392    /// other allocation that could trigger GC.
393    #[inline]
394    pub unsafe fn alloc_list(n: R_xlen_t) -> SEXP {
395        unsafe { Rf_allocVector(SEXPTYPE::VECSXP, n) }
396    }
397
398    /// Allocate an R character vector (STRSXP) of length `n`. Unprotected.
399    ///
400    /// Equivalent to `Rf_allocVector(STRSXP, n)`. Elements are initialised to `R_BlankString`.
401    ///
402    /// # Safety
403    ///
404    /// Must be called from the R main thread. The returned SEXP is unprotected —
405    /// wrap it in [`OwnedProtect`](crate::gc_protect::OwnedProtect) before any
406    /// other allocation that could trigger GC.
407    #[inline]
408    pub unsafe fn alloc_strsxp(n: R_xlen_t) -> SEXP {
409        unsafe { Rf_allocVector(SEXPTYPE::STRSXP, n) }
410    }
411
412    // endregion
413
414    // region: R global symbols and singletons
415
416    /// R's `names` attribute symbol.
417    #[inline]
418    pub fn names_symbol() -> SEXP {
419        unsafe { R_NamesSymbol }
420    }
421
422    /// R's `dim` attribute symbol.
423    #[inline]
424    pub fn dim_symbol() -> SEXP {
425        unsafe { R_DimSymbol }
426    }
427
428    /// R's `dimnames` attribute symbol.
429    #[inline]
430    pub fn dimnames_symbol() -> SEXP {
431        unsafe { R_DimNamesSymbol }
432    }
433
434    /// R's `class` attribute symbol.
435    #[inline]
436    pub fn class_symbol() -> SEXP {
437        unsafe { R_ClassSymbol }
438    }
439
440    /// R's `levels` attribute symbol (factors).
441    #[inline]
442    pub fn levels_symbol() -> SEXP {
443        unsafe { R_LevelsSymbol }
444    }
445
446    /// R's `tsp` attribute symbol (time series).
447    #[inline]
448    pub fn tsp_symbol() -> SEXP {
449        unsafe { R_TspSymbol }
450    }
451
452    /// R's base namespace environment.
453    #[inline]
454    pub fn base_namespace() -> SEXP {
455        unsafe { R_BaseNamespace }
456    }
457
458    /// R's missing argument sentinel.
459    #[inline]
460    pub fn missing_arg() -> SEXP {
461        unsafe { R_MissingArg }
462    }
463
464    // endregion
465
466    // region: ALTREP data slot access
467
468    /// Get the raw SEXP in the ALTREP data1 slot.
469    ///
470    /// # Safety
471    ///
472    /// - `self` must be a valid ALTREP SEXP
473    /// - Must be called from the R main thread
474    #[inline]
475    pub unsafe fn altrep_data1_raw(self) -> SEXP {
476        unsafe { R_altrep_data1(self) }
477    }
478
479    /// Get the raw SEXP in the ALTREP data1 slot (unchecked — no thread routing).
480    ///
481    /// # Safety
482    ///
483    /// - `self` must be a valid ALTREP SEXP
484    /// - Must be called from the R main thread
485    #[inline]
486    pub unsafe fn altrep_data1_raw_unchecked(self) -> SEXP {
487        unsafe { R_altrep_data1_unchecked(self) }
488    }
489
490    /// Set the ALTREP data1 slot.
491    ///
492    /// # Safety
493    ///
494    /// - `self` must be a valid ALTREP SEXP
495    /// - Must be called from the R main thread
496    #[inline]
497    pub unsafe fn set_altrep_data1(self, v: SEXP) {
498        unsafe { R_set_altrep_data1(self, v) }
499    }
500
501    /// Get the raw SEXP in the ALTREP data2 slot.
502    ///
503    /// # Safety
504    ///
505    /// - `self` must be a valid ALTREP SEXP
506    /// - Must be called from the R main thread
507    #[inline]
508    pub unsafe fn altrep_data2_raw(self) -> SEXP {
509        unsafe { R_altrep_data2(self) }
510    }
511
512    /// Get the raw SEXP in the ALTREP data2 slot (unchecked — no thread routing).
513    ///
514    /// # Safety
515    ///
516    /// - `self` must be a valid ALTREP SEXP
517    /// - Must be called from the R main thread
518    #[inline]
519    pub unsafe fn altrep_data2_raw_unchecked(self) -> SEXP {
520        unsafe { R_altrep_data2_unchecked(self) }
521    }
522
523    /// Set the ALTREP data2 slot.
524    ///
525    /// # Safety
526    ///
527    /// - `self` must be a valid ALTREP SEXP
528    /// - Must be called from the R main thread
529    #[inline]
530    pub unsafe fn set_altrep_data2(self, v: SEXP) {
531        unsafe { R_set_altrep_data2(self, v) }
532    }
533
534    /// Set the ALTREP data2 slot (unchecked — no thread routing).
535    ///
536    /// # Safety
537    ///
538    /// - `self` must be a valid ALTREP SEXP
539    /// - Must be called from the R main thread
540    #[inline]
541    pub unsafe fn set_altrep_data2_unchecked(self, v: SEXP) {
542        unsafe { R_set_altrep_data2_unchecked(self, v) }
543    }
544
545    // endregion
546}
547
548impl Default for SEXP {
549    #[inline]
550    fn default() -> Self {
551        Self::null()
552    }
553}
554
555impl From<*mut SEXPREC> for SEXP {
556    #[inline]
557    fn from(ptr: *mut SEXPREC) -> Self {
558        Self(ptr)
559    }
560}
561
562impl From<SEXP> for *mut SEXPREC {
563    #[inline]
564    fn from(sexp: SEXP) -> Self {
565        sexp.0
566    }
567}