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