Skip to main content

miniextendr_api/
altrep.rs

1//! Core ALTREP types and registration traits.
2//!
3//! ## Architecture
4//!
5//! - **FFI**: Raw setters/types in `crate::sys::altrep`
6//! - **Traits**: Safe traits in [`crate::altrep_traits`] (`Altrep`, `AltVec`, `AltInteger`, etc.)
7//!   - Required methods: Compiler-enforced by trait definition
8//!   - Optional methods: Gated by HAS_* constants, defaults provided
9//! - **Bridge**: Generic `extern "C-unwind"` trampolines in [`crate::altrep_bridge`]
10//! - **High-level data traits**: [`crate::altrep_data`] (`AltrepLen` +
11//!   `Alt*Data`) — implement with `&self` methods instead of raw SEXP.
12//! - **SEXP wrapper**: [`crate::altrep_sexp`] — `!Send + !Sync` wrapper that
13//!   prevents an ALTREP SEXP from crossing thread boundaries before
14//!   materialization.
15//! - **Derive**: `#[derive(AltrepInteger)]` / `AltrepReal` / `AltrepLogical`
16//!   / `AltrepRaw` / `AltrepString` / `AltrepComplex` / `AltrepList` on a
17//!   struct emits everything below: `Altrep`, `AltVec`, the family-specific
18//!   trait, `AltrepLen`, `Alt*Data`, an `impl RegisterAltrep`, and the
19//!   `R_make_alt*` registration. See the `altrep_derive` module in the
20//!   `miniextendr-macros` crate for the attribute reference and validation
21//!   rules.
22//!
23//! ## Two code paths
24//!
25//! 1. **Field-based derive** (default) — annotate fields with
26//!    `#[altrep(len = "field", elt = "field", class = "Name")]` and the
27//!    derive handles `length`/`elt`/registration.
28//! 2. **Manual** — `#[altrep(manual)]` skips `AltrepLen` / `Alt*Data` so you
29//!    hand-roll those traits (custom `elt`, `no_na`, `sum`, …); the
30//!    `impl_alt*_from_data!` registration is still emitted.
31//!
32//! Both paths route trampolines through the same guard mode selected by
33//! [`Altrep::GUARD`](crate::altrep_traits::Altrep::GUARD); guard modes are
34//! documented on [`crate::altrep_traits`].
35//!
36//! ## Threading
37//!
38//! ALTREP callbacks run on R's main thread — they receive raw `SEXP`
39//! arguments which are not `Send`. Do not route them through the worker
40//! thread; the trampolines bridge from C straight back into safe Rust on the
41//! main thread, with panic / longjmp handling per the chosen guard mode.
42
43use crate::sys::altrep::{
44    R_altrep_class_t, R_make_altcomplex_class, R_make_altinteger_class, R_make_altlist_class,
45    R_make_altlogical_class, R_make_altraw_class, R_make_altreal_class, R_make_altstring_class,
46};
47use std::ffi::CStr;
48use std::sync::Mutex;
49
50/// Base type for ALTREP vectors.
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum RBase {
53    /// Integer vectors (`INTSXP`).
54    Int,
55    /// Double vectors (`REALSXP`).
56    Real,
57    /// Logical vectors (`LGLSXP`).
58    Logical,
59    /// Raw byte vectors (`RAWSXP`).
60    Raw,
61    /// Character vectors (`STRSXP`).
62    String,
63    /// Generic list vectors (`VECSXP`).
64    List,
65    /// Complex vectors (`CPLXSXP`).
66    Complex,
67}
68
69impl RBase {
70    /// The [`SEXPTYPE`](crate::SEXPTYPE) an ALTREP vector of this base
71    /// presents to R.
72    pub const fn sexptype(self) -> crate::SEXPTYPE {
73        match self {
74            RBase::Int => crate::SEXPTYPE::INTSXP,
75            RBase::Real => crate::SEXPTYPE::REALSXP,
76            RBase::Logical => crate::SEXPTYPE::LGLSXP,
77            RBase::Raw => crate::SEXPTYPE::RAWSXP,
78            RBase::String => crate::SEXPTYPE::STRSXP,
79            RBase::List => crate::SEXPTYPE::VECSXP,
80            RBase::Complex => crate::SEXPTYPE::CPLXSXP,
81        }
82    }
83}
84
85/// Trait implemented by ALTREP classes via `#[miniextendr]`.
86///
87/// This trait is automatically implemented when using the proc-macro with
88/// ALTREP attributes (class, pkg, base).
89pub trait AltrepClass {
90    /// The class name (null-terminated C string).
91    const CLASS_NAME: &'static std::ffi::CStr;
92    /// The base R type (Int, Real, Logical, etc.).
93    const BASE: RBase;
94}
95
96/// Registration trait: implemented per type by the macro on struct items.
97///
98/// The `get_or_init_class` method returns the ALTREP class handle, initializing
99/// it on first call and returning the cached handle on subsequent calls.
100///
101/// This trait combines class creation and method installation into a single
102/// `get_or_init_class` call that caches the result.
103pub trait RegisterAltrep {
104    /// Get the ALTREP class handle, initializing it if this is the first call.
105    ///
106    /// The implementation should:
107    /// 1. Create the class handle via `R_make_alt*` (or via `InferBase::make_class`)
108    /// 2. Install methods via `install_*` functions from `altrep_bridge`
109    /// 3. Cache the result in a static `OnceLock`
110    fn get_or_init_class() -> R_altrep_class_t;
111}
112
113// region: Runtime dispatch helper for class creation
114
115/// Records every ALTREP class name registered during `package_init()`.
116///
117/// After all registrations complete, [`assert_altrep_class_uniqueness`] checks
118/// for duplicates. Using `Mutex` rather than `RefCell` because `validate_altrep_class`
119/// can be called from any context during init.
120static REGISTERED_CLASS_NAMES: Mutex<Vec<String>> = Mutex::new(Vec::new());
121
122/// Validate that an ALTREP class handle was successfully created.
123///
124/// Panics with a descriptive message if the class handle is null, indicating
125/// that `R_make_alt*_class()` failed during registration.
126///
127/// Also records the class name for later duplicate detection via
128/// [`assert_altrep_class_uniqueness`].
129///
130/// # Arguments
131/// * `cls` - The class handle returned by `R_make_alt*_class()`
132/// * `class_name` - The name of the ALTREP class (for diagnostics)
133/// * `base` - The base R type (for diagnostics)
134pub fn validate_altrep_class(
135    cls: R_altrep_class_t,
136    class_name: &CStr,
137    base: RBase,
138) -> R_altrep_class_t {
139    if cls.ptr.is_null() {
140        panic!(
141            "ALTREP class registration failed: R_make_alt{base:?}_class() returned NULL \
142             for class {:?}",
143            class_name
144        );
145    }
146    // Record the name for duplicate detection at the end of package_init().
147    REGISTERED_CLASS_NAMES
148        .lock()
149        .expect("REGISTERED_CLASS_NAMES poisoned")
150        .push(class_name.to_string_lossy().into_owned());
151    cls
152}
153
154/// Assert that all registered ALTREP class names are unique.
155///
156/// Must be called after all ALTREP registrations (builtin, arrow, user-defined)
157/// have completed in `package_init()`. Panics with a clear message if any
158/// duplicate class name is found.
159pub fn assert_altrep_class_uniqueness() {
160    let names = REGISTERED_CLASS_NAMES
161        .lock()
162        .expect("REGISTERED_CLASS_NAMES poisoned");
163    if names.len() <= 1 {
164        return;
165    }
166    // Sort + dedup to find collisions efficiently.
167    let mut sorted: Vec<&str> = names.iter().map(|s| s.as_str()).collect();
168    sorted.sort_unstable();
169    for window in sorted.windows(2) {
170        if window[0] == window[1] {
171            panic!(
172                "miniextendr: duplicate ALTREP class name \"{}\" \
173                 — each ALTREP type must have a unique class name within the package",
174                window[0]
175            );
176        }
177    }
178}
179
180/// Create an ALTREP class handle based on the runtime base type.
181///
182/// Validates the returned handle and panics if registration fails.
183///
184/// # Safety
185/// Must be called during R initialization (after `set_altrep_dll_info`).
186pub unsafe fn make_class_by_base(
187    class_name: *const i8,
188    pkg_name: *const i8,
189    base: RBase,
190) -> R_altrep_class_t {
191    let dll = crate::altrep_dll_info();
192    let cls = unsafe {
193        match base {
194            RBase::Int => R_make_altinteger_class(class_name, pkg_name, dll),
195            RBase::Real => R_make_altreal_class(class_name, pkg_name, dll),
196            RBase::Logical => R_make_altlogical_class(class_name, pkg_name, dll),
197            RBase::Raw => R_make_altraw_class(class_name, pkg_name, dll),
198            RBase::String => R_make_altstring_class(class_name, pkg_name, dll),
199            RBase::List => R_make_altlist_class(class_name, pkg_name, dll),
200            RBase::Complex => R_make_altcomplex_class(class_name, pkg_name, dll),
201        }
202    };
203    // SAFETY: class_name was passed to R, so it's still a valid C string
204    let name_cstr = unsafe { CStr::from_ptr(class_name) };
205    validate_altrep_class(cls, name_cstr, base)
206}
207// endregion