miniextendr_api/trait_abi.rs
1//! # Trait ABI Runtime Support
2//!
3//! This module provides runtime support for cross-package trait dispatch.
4//! It bridges between R's external pointer system and Rust's trait objects
5//! using a stable C ABI.
6//!
7//! ## Overview
8//!
9//! The trait ABI system enables:
10//!
11//! 1. **Cross-package dispatch**: Package A can call trait methods on objects
12//! created by Package B, without compile-time knowledge of the concrete type.
13//!
14//! 2. **Type safety**: Runtime type checking via [`mx_tag`] ensures safe downcasts.
15//!
16//! 3. **Memory safety**: R's garbage collector manages object lifetime via
17//! external pointer finalizers.
18//!
19//! ## Architecture
20//!
21//! ```text
22//! ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
23//! │ R Code │ │ C-callables │ │ Rust Runtime │
24//! │ │ │ (rpkg) │ │ (miniextendr) │
25//! │ .Call("method", │────►│ mx_query() │────►│ vtable lookup │
26//! │ obj, ...) │ │ mx_wrap() │ │ method shim │
27//! │ │◄────│ mx_get() │◄────│ type conversion │
28//! └─────────────────┘ └──────────────────┘ └─────────────────┘
29//! ```
30//!
31//! ## Submodules
32//!
33//! - [`ccall`]: Direct FFI wrappers for `mx_abi.rs` functions
34//! - [`conv`]: Type conversion helpers for method shims
35//!
36//! ## Integration with ExternalPtr / TypedExternal
37//!
38//! Trait ABI support is integrated with the [`ExternalPtr`] and [`TypedExternal`]
39//! system. `ExternalPtr<T>` serves as the "traitless" case (equivalent to `Any`
40//! in dynamic typing). Trait dispatch wrappers are automatically registered
41//! when you annotate `impl Trait for Type` with `#[miniextendr]`:
42//!
43//! ```ignore
44//! #[derive(ExternalPtr)]
45//! struct Circle { radius: f64 }
46//!
47//! #[miniextendr]
48//! impl Shape for Circle { /* ... */ }
49//! ```
50//!
51//! This generates the wrapper structures, vtable references, and query
52//! implementations for cross-package trait dispatch.
53//!
54//! ## Exporting traits you don't own
55//!
56//! You cannot apply `#[miniextendr]` to external traits. Instead, define a
57//! **local adapter trait** that exposes the subset you want in R, then provide
58//! a blanket impl for any type that implements the external trait:
59//!
60//! ```ignore
61//! use num_traits::Num;
62//!
63//! #[miniextendr]
64//! pub trait RNum {
65//! fn add(&self, other: &Self) -> Self;
66//! fn to_string(&self) -> String;
67//! }
68//!
69//! impl<T: Num + Clone + ToString> RNum for T {
70//! fn add(&self, other: &Self) -> Self { self.clone() + other.clone() }
71//! fn to_string(&self) -> String { ToString::to_string(self) }
72//! }
73//! ```
74//!
75//! This keeps the ABI stable while avoiding generics in the trait itself.
76//!
77//! ## Initialization
78//!
79//! Each package includes `mx_abi.rs` (from miniextendr-api) which provides the
80//! `mx_wrap`/`mx_get`/`mx_query` functions. `package_init()` (via `miniextendr_init!`)
81//! calls `mx_abi_register()` to initialize the tag symbol and register C-callables.
82//!
83//! ## Thread Safety
84//!
85//! All trait ABI operations are **main-thread only**:
86//!
87//! - R invokes `.Call` on the main thread
88//! - Method shims do not route through `with_r_thread`
89//!
90//! ## Example Usage
91//!
92//! ### Defining a trait (provider package)
93//!
94//! ```ignore
95//! // In package "shapes"
96//! #[miniextendr]
97//! pub trait Shape {
98//! fn area(&self) -> f64;
99//! fn perimeter(&self) -> f64;
100//! }
101//!
102//! #[derive(ExternalPtr)]
103//! pub struct Circle { radius: f64 }
104//!
105//! #[miniextendr]
106//! impl Shape for Circle {
107//! fn area(&self) -> f64 { std::f64::consts::PI * self.radius * self.radius }
108//! fn perimeter(&self) -> f64 { 2.0 * std::f64::consts::PI * self.radius }
109//! }
110//! ```
111//!
112//! ### Using across packages (consumer package)
113//!
114//! ```ignore
115//! // In package "geometry" (depends on "shapes")
116//! use shapes::{TAG_SHAPE, ShapeView};
117//!
118//! fn calculate_area(obj: SEXP) -> f64 {
119//! unsafe {
120//! let view = mx_query_as::<ShapeView>(obj, TAG_SHAPE)
121//! .expect("object does not implement Shape");
122//! // Call method through vtable
123//! view.area()
124//! }
125//! }
126//! ```
127//!
128//! [`ccall`]: crate::trait_abi::ccall
129//! [`conv`]: crate::trait_abi::conv
130//! [`mx_tag`]: crate::abi::mx_tag
131//! [`ExternalPtr`]: crate::externalptr::ExternalPtr
132//! [`TypedExternal`]: crate::externalptr::TypedExternal
133
134pub mod ccall;
135pub mod conv;
136
137// Re-export commonly used items
138pub use conv::{check_arity, extract_arg, from_sexp, nil, rf_error, to_sexp, try_from_sexp};
139
140use crate::abi::mx_tag;
141use crate::ffi::SEXP;
142use std::os::raw::c_void;
143
144// region: TraitView - Trait for macro-generated View structs
145
146/// Trait for view types that can be created from SEXP via trait dispatch.
147///
148/// This trait is implemented by the macro-generated `<Trait>View` structs.
149/// It provides a common interface for:
150/// - Querying whether an object implements a trait
151/// - Creating a view from an SEXP
152///
153/// # Generated by `#[miniextendr]` on traits
154///
155/// When you write:
156/// ```ignore
157/// #[miniextendr]
158/// pub trait Counter {
159/// fn value(&self) -> i32;
160/// fn increment(&mut self);
161/// }
162/// ```
163///
164/// The macro generates `CounterView` that implements `TraitView`:
165/// ```ignore
166/// impl TraitView for CounterView {
167/// const TAG: mx_tag = TAG_COUNTER;
168///
169/// unsafe fn from_raw_parts(data: *mut c_void, vtable: *const c_void) -> Self {
170/// Self {
171/// data,
172/// vtable: vtable.cast::<CounterVTable>(),
173/// }
174/// }
175/// }
176/// ```
177///
178/// # Usage
179///
180/// ```ignore
181/// // Try to get a Counter view from an R object
182/// let view = CounterView::try_from_sexp(obj)?;
183///
184/// // Call methods through the view
185/// view.increment();
186/// let val = view.value();
187/// ```
188///
189/// # Safety
190///
191/// The `from_raw_parts` method is unsafe because:
192/// - `data` must be a valid pointer to the concrete object
193/// - `vtable` must be a valid pointer to the trait's vtable
194/// - The pointers must remain valid for the lifetime of the view
195pub trait TraitView: Sized {
196 /// The type tag for this trait.
197 ///
198 /// This is a compile-time constant generated by `#[miniextendr]` on the trait.
199 const TAG: mx_tag;
200
201 /// Create a view from raw data and vtable pointers.
202 ///
203 /// # Safety
204 ///
205 /// - `data` must be a valid, non-null pointer to the concrete object
206 /// - `vtable` must be a valid, non-null pointer to the trait's vtable
207 /// - Both pointers must remain valid for the lifetime of the view
208 unsafe fn from_raw_parts(data: *mut c_void, vtable: *const c_void) -> Self;
209
210 /// Try to create a view from an R SEXP.
211 ///
212 /// Queries the object for this trait's vtable using `mx_query`. If the
213 /// object implements the trait, returns `Some(view)`. Otherwise returns `None`.
214 ///
215 /// # Safety
216 ///
217 /// - `sexp` must be a valid R external pointer (EXTPTRSXP)
218 /// - Must be called on R's main thread
219 ///
220 /// # Returns
221 ///
222 /// - `Some(Self)` if the object implements the trait
223 /// - `None` if the object does not implement the trait
224 ///
225 /// # Example
226 ///
227 /// ```ignore
228 /// let counter = unsafe { CounterView::try_from_sexp(obj) }
229 /// .expect("Object does not implement Counter");
230 /// ```
231 #[inline]
232 unsafe fn try_from_sexp(sexp: SEXP) -> Option<Self> {
233 // SAFETY: Caller guarantees sexp is valid and we're on main thread
234 unsafe {
235 // Get the vtable for this trait
236 let vtable = ccall::mx_query(sexp, Self::TAG);
237 if vtable.is_null() {
238 return None;
239 }
240
241 // Get the erased pointer (points to the wrapper struct header)
242 let raw_ptr = crate::ffi::R_ExternalPtrAddr(sexp);
243 if raw_ptr.is_null() {
244 return None;
245 }
246 let erased_ptr = raw_ptr.cast::<crate::abi::mx_erased>();
247
248 // The wrapper struct layout is:
249 // #[repr(C)]
250 // struct __MxWrapper<T> {
251 // erased: mx_erased, // offset 0
252 // data: T, // offset = size_of::<mx_erased>() rounded up to align_of::<T>()
253 // }
254 // When T has stricter alignment than mx_erased, padding exists between
255 // erased and data. The correct offset is stored in the base vtable.
256 let base = (*erased_ptr).base;
257 let data_offset = (*base).data_offset;
258 let data = erased_ptr.cast::<u8>().add(data_offset).cast::<c_void>();
259
260 Some(Self::from_raw_parts(data, vtable))
261 }
262 }
263
264 /// Try to create a view, returning an error message on failure.
265 ///
266 /// Similar to `try_from_sexp` but returns an error string suitable for
267 /// use as an R error message if the object does not implement the trait.
268 ///
269 /// # Safety
270 ///
271 /// Same as `try_from_sexp`.
272 #[inline]
273 unsafe fn try_from_sexp_or_error(sexp: SEXP, trait_name: &str) -> Result<Self, String> {
274 // SAFETY: Delegated to try_from_sexp
275 unsafe {
276 Self::try_from_sexp(sexp)
277 .ok_or_else(|| format!("Object does not implement {} trait", trait_name))
278 }
279 }
280}
281// endregion