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::ffi::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//! - **Macro**: `#[miniextendr]` on a struct emits `impl RegisterAltrep` that:
11//!   - Creates the class handle via `R_make_alt*`
12//!   - Installs methods based on trait bounds and HAS_* consts
13
14use crate::ffi::altrep::{
15    R_altrep_class_t, R_make_altcomplex_class, R_make_altinteger_class, R_make_altlist_class,
16    R_make_altlogical_class, R_make_altraw_class, R_make_altreal_class, R_make_altstring_class,
17};
18use std::ffi::CStr;
19use std::sync::Mutex;
20
21/// Base type for ALTREP vectors.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum RBase {
24    /// Integer vectors (`INTSXP`).
25    Int,
26    /// Double vectors (`REALSXP`).
27    Real,
28    /// Logical vectors (`LGLSXP`).
29    Logical,
30    /// Raw byte vectors (`RAWSXP`).
31    Raw,
32    /// Character vectors (`STRSXP`).
33    String,
34    /// Generic list vectors (`VECSXP`).
35    List,
36    /// Complex vectors (`CPLXSXP`).
37    Complex,
38}
39
40/// Trait implemented by ALTREP classes via `#[miniextendr]`.
41///
42/// This trait is automatically implemented when using the proc-macro with
43/// ALTREP attributes (class, pkg, base).
44pub trait AltrepClass {
45    /// The class name (null-terminated C string).
46    const CLASS_NAME: &'static std::ffi::CStr;
47    /// The base R type (Int, Real, Logical, etc.).
48    const BASE: RBase;
49}
50
51/// Registration trait: implemented per type by the macro on struct items.
52///
53/// The `get_or_init_class` method returns the ALTREP class handle, initializing
54/// it on first call and returning the cached handle on subsequent calls.
55///
56/// This trait combines class creation and method installation into a single
57/// `get_or_init_class` call that caches the result.
58pub trait RegisterAltrep {
59    /// Get the ALTREP class handle, initializing it if this is the first call.
60    ///
61    /// The implementation should:
62    /// 1. Create the class handle via `R_make_alt*` (or via `InferBase::make_class`)
63    /// 2. Install methods via `install_*` functions from `altrep_bridge`
64    /// 3. Cache the result in a static `OnceLock`
65    fn get_or_init_class() -> R_altrep_class_t;
66}
67
68// region: Runtime dispatch helper for class creation
69
70/// Records every ALTREP class name registered during `package_init()`.
71///
72/// After all registrations complete, [`assert_altrep_class_uniqueness`] checks
73/// for duplicates. Using `Mutex` rather than `RefCell` because `validate_altrep_class`
74/// can be called from any context during init.
75static REGISTERED_CLASS_NAMES: Mutex<Vec<String>> = Mutex::new(Vec::new());
76
77/// Validate that an ALTREP class handle was successfully created.
78///
79/// Panics with a descriptive message if the class handle is null, indicating
80/// that `R_make_alt*_class()` failed during registration.
81///
82/// Also records the class name for later duplicate detection via
83/// [`assert_altrep_class_uniqueness`].
84///
85/// # Arguments
86/// * `cls` - The class handle returned by `R_make_alt*_class()`
87/// * `class_name` - The name of the ALTREP class (for diagnostics)
88/// * `base` - The base R type (for diagnostics)
89pub fn validate_altrep_class(
90    cls: R_altrep_class_t,
91    class_name: &CStr,
92    base: RBase,
93) -> R_altrep_class_t {
94    if cls.ptr.is_null() {
95        panic!(
96            "ALTREP class registration failed: R_make_alt{base:?}_class() returned NULL \
97             for class {:?}",
98            class_name
99        );
100    }
101    // Record the name for duplicate detection at the end of package_init().
102    REGISTERED_CLASS_NAMES
103        .lock()
104        .expect("REGISTERED_CLASS_NAMES poisoned")
105        .push(class_name.to_string_lossy().into_owned());
106    cls
107}
108
109/// Assert that all registered ALTREP class names are unique.
110///
111/// Must be called after all ALTREP registrations (builtin, arrow, user-defined)
112/// have completed in `package_init()`. Panics with a clear message if any
113/// duplicate class name is found.
114pub fn assert_altrep_class_uniqueness() {
115    let names = REGISTERED_CLASS_NAMES
116        .lock()
117        .expect("REGISTERED_CLASS_NAMES poisoned");
118    if names.len() <= 1 {
119        return;
120    }
121    // Sort + dedup to find collisions efficiently.
122    let mut sorted: Vec<&str> = names.iter().map(|s| s.as_str()).collect();
123    sorted.sort_unstable();
124    for window in sorted.windows(2) {
125        if window[0] == window[1] {
126            panic!(
127                "miniextendr: duplicate ALTREP class name \"{}\" \
128                 — each ALTREP type must have a unique class name within the package",
129                window[0]
130            );
131        }
132    }
133}
134
135/// Create an ALTREP class handle based on the runtime base type.
136///
137/// Validates the returned handle and panics if registration fails.
138///
139/// # Safety
140/// Must be called during R initialization (after `set_altrep_dll_info`).
141pub unsafe fn make_class_by_base(
142    class_name: *const i8,
143    pkg_name: *const i8,
144    base: RBase,
145) -> R_altrep_class_t {
146    let dll = crate::altrep_dll_info();
147    let cls = unsafe {
148        match base {
149            RBase::Int => R_make_altinteger_class(class_name, pkg_name, dll),
150            RBase::Real => R_make_altreal_class(class_name, pkg_name, dll),
151            RBase::Logical => R_make_altlogical_class(class_name, pkg_name, dll),
152            RBase::Raw => R_make_altraw_class(class_name, pkg_name, dll),
153            RBase::String => R_make_altstring_class(class_name, pkg_name, dll),
154            RBase::List => R_make_altlist_class(class_name, pkg_name, dll),
155            RBase::Complex => R_make_altcomplex_class(class_name, pkg_name, dll),
156        }
157    };
158    // SAFETY: class_name was passed to R, so it's still a valid C string
159    let name_cstr = unsafe { CStr::from_ptr(class_name) };
160    validate_altrep_class(cls, name_cstr, base)
161}
162// endregion