miniextendr_api/trait_abi/ccall.rs
1//! # Direct FFI to mx_abi Functions
2//!
3//! This module provides Rust wrappers around the `#[no_mangle]` functions defined in
4//! `mx_abi.rs`, which is compiled into each package's static library.
5//!
6//! ## Functions
7//!
8//! | Function | Purpose |
9//! |----------|---------|
10//! | `mx_wrap` | Wrap `*mut mx_erased` in R's EXTPTRSXP |
11//! | `mx_get` | Extract `*mut mx_erased` from EXTPTRSXP |
12//! | `mx_query` | Query for interface vtable by tag |
13//!
14//! ## Linkage
15//!
16//! Each package includes `mx_abi.rs` in its staticlib, so these symbols are
17//! resolved at link time — no `R_GetCCallable` indirection needed.
18//!
19//! ## Thread Safety
20//!
21//! All wrapper functions must be called from R's main thread.
22
23use crate::abi::{mx_erased, mx_tag};
24use crate::ffi::SEXP;
25use std::os::raw::c_void;
26
27// region: Direct extern declarations to mx_abi.rs functions (same .so)
28
29mod ffi {
30 use super::*;
31
32 unsafe extern "C" {
33 pub(super) fn mx_wrap(ptr: *mut mx_erased) -> SEXP;
34 pub(super) fn mx_get(sexp: SEXP) -> *mut mx_erased;
35 pub(super) fn mx_query(sexp: SEXP, tag: mx_tag) -> *const c_void;
36 }
37}
38// endregion
39
40// region: Wrapper functions
41
42/// Wrap an erased object pointer in an R external pointer.
43///
44/// Creates an R `EXTPTRSXP` that wraps the given erased object. The external
45/// pointer's finalizer will call the object's `drop` function when garbage
46/// collected.
47///
48/// # Arguments
49///
50/// * `ptr` - Pointer to erased object (must be heap-allocated)
51///
52/// # Returns
53///
54/// R external pointer (`EXTPTRSXP`) containing the erased object.
55///
56/// # Safety
57///
58/// - `ptr` must be a valid pointer to `mx_erased`
59/// - `ptr` must be heap-allocated (will be freed by finalizer)
60/// - Must be called on R's main thread
61/// - `mx_abi_register()` must have been called (via `miniextendr_init!`)
62///
63/// # Example
64///
65/// ```ignore
66/// // In constructor
67/// let obj = Box::into_raw(Box::new(MyErasedWrapper::new(data)));
68/// let sexp = unsafe { mx_wrap(obj.cast::<mx_erased>()) };
69/// ```
70#[inline]
71pub unsafe fn mx_wrap(ptr: *mut mx_erased) -> SEXP {
72 // SAFETY: Caller guarantees ptr is valid and we're on main thread.
73 // mx_wrap is linked from mx_abi.rs in the same .so.
74 unsafe { ffi::mx_wrap(ptr) }
75}
76
77/// Extract an erased object pointer from an R external pointer.
78///
79/// Retrieves the `*mut mx_erased` stored in an R `EXTPTRSXP`.
80///
81/// # Arguments
82///
83/// * `sexp` - R external pointer created by [`mx_wrap`]
84///
85/// # Returns
86///
87/// Pointer to the erased object, or null if:
88/// - `sexp` is not an external pointer
89/// - The external pointer has been invalidated
90///
91/// # Safety
92///
93/// - `sexp` must be a valid SEXP
94/// - Must be called on R's main thread
95/// - The returned pointer is only valid while R protects the SEXP
96#[inline]
97pub unsafe fn mx_get(sexp: SEXP) -> *mut mx_erased {
98 // SAFETY: Caller guarantees sexp is valid and we're on main thread.
99 unsafe { ffi::mx_get(sexp) }
100}
101
102/// Query an object for an interface vtable by tag.
103///
104/// Looks up whether the object implements the trait identified by `tag`,
105/// and returns a pointer to the vtable if so.
106///
107/// # Arguments
108///
109/// * `sexp` - R external pointer wrapping an erased object
110/// * `tag` - Tag identifying the requested trait interface
111///
112/// # Returns
113///
114/// - Non-null pointer to the trait's vtable if implemented
115/// - Null pointer if:
116/// - `sexp` is not a valid erased object
117/// - The object does not implement the requested trait
118///
119/// # Safety
120///
121/// - `sexp` must be a valid SEXP
122/// - Must be called on R's main thread
123/// - The returned pointer must be cast to the correct vtable type
124///
125/// # Example
126///
127/// ```ignore
128/// let vtable = unsafe { mx_query(obj, TAG_FOO) };
129/// if !vtable.is_null() {
130/// let foo_vtable = vtable.cast::<FooVTable>();
131/// // Call method through vtable...
132/// }
133/// ```
134#[inline]
135pub unsafe fn mx_query(sexp: SEXP, tag: mx_tag) -> *const c_void {
136 // SAFETY: Caller guarantees sexp is valid and we're on main thread.
137 unsafe { ffi::mx_query(sexp, tag) }
138}
139
140/// Query an object for an interface and return a typed view.
141///
142/// Looks up `tag` in the object's vtable registry and, if found, returns
143/// `Some(&V)` pointing to the data pointer + vtable pair for the trait.
144///
145/// # Type Parameters
146///
147/// * `V` - The view type (e.g., `FooView`) containing data pointer and vtable
148///
149/// # Arguments
150///
151/// * `sexp` - R external pointer wrapping an erased object
152/// * `tag` - Tag identifying the requested trait interface
153///
154/// # Returns
155///
156/// - `Some(&V)` if the object implements the trait
157/// - `None` if the object does not implement the trait
158///
159/// # Safety
160///
161/// - `sexp` must be a valid SEXP
162/// - `V` must be the correct view type for `tag`
163/// - Must be called on R's main thread
164///
165/// # Example
166///
167/// ```ignore
168/// if let Some(view) = unsafe { mx_query_as::<FooView>(obj, TAG_FOO) } {
169/// let result = view.some_method(args);
170/// } else {
171/// panic!("object does not implement Foo");
172/// }
173/// ```
174#[inline]
175pub unsafe fn mx_query_as<V>(sexp: SEXP, tag: mx_tag) -> Option<&'static V> {
176 let vtable = unsafe { mx_query(sexp, tag) };
177 if vtable.is_null() {
178 None
179 } else {
180 Some(unsafe { &*vtable.cast::<V>() })
181 }
182}
183// endregion