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
140// Re-export for use in macro-generated View method wrappers (Approach 1, issue #345).
141// The View method wrapper calls this immediately after the vtable call to re-panic
142// with the reconstructed RCondition if the shim returned a tagged error SEXP.
143pub use crate::condition::repanic_if_rust_error;
144
145use crate::abi::mx_tag;
146use crate::ffi::SEXP;
147use std::os::raw::c_void;
148
149// region: TraitView - Trait for macro-generated View structs
150
151/// Trait for view types that can be created from SEXP via trait dispatch.
152///
153/// This trait is implemented by the macro-generated `<Trait>View` structs.
154/// It provides a common interface for:
155/// - Querying whether an object implements a trait
156/// - Creating a view from an SEXP
157///
158/// # Generated by `#[miniextendr]` on traits
159///
160/// When you write:
161/// ```ignore
162/// #[miniextendr]
163/// pub trait Counter {
164/// fn value(&self) -> i32;
165/// fn increment(&mut self);
166/// }
167/// ```
168///
169/// The macro generates `CounterView` that implements `TraitView`:
170/// ```ignore
171/// impl TraitView for CounterView {
172/// const TAG: mx_tag = TAG_COUNTER;
173///
174/// unsafe fn from_raw_parts(data: *mut c_void, vtable: *const c_void) -> Self {
175/// Self {
176/// data,
177/// vtable: vtable.cast::<CounterVTable>(),
178/// }
179/// }
180/// }
181/// ```
182///
183/// # Usage
184///
185/// ```ignore
186/// // Try to get a Counter view from an R object
187/// let view = CounterView::try_from_sexp(obj)?;
188///
189/// // Call methods through the view
190/// view.increment();
191/// let val = view.value();
192/// ```
193///
194/// # Safety
195///
196/// The `from_raw_parts` method is unsafe because:
197/// - `data` must be a valid pointer to the concrete object
198/// - `vtable` must be a valid pointer to the trait's vtable
199/// - The pointers must remain valid for the lifetime of the view
200pub trait TraitView: Sized {
201 /// The type tag for this trait.
202 ///
203 /// This is a compile-time constant generated by `#[miniextendr]` on the trait.
204 const TAG: mx_tag;
205
206 /// Create a view from raw data and vtable pointers.
207 ///
208 /// # Safety
209 ///
210 /// - `data` must be a valid, non-null pointer to the concrete object
211 /// - `vtable` must be a valid, non-null pointer to the trait's vtable
212 /// - Both pointers must remain valid for the lifetime of the view
213 unsafe fn from_raw_parts(data: *mut c_void, vtable: *const c_void) -> Self;
214
215 /// Try to create a view from an R SEXP.
216 ///
217 /// Queries the object for this trait's vtable using `mx_query`. If the
218 /// object implements the trait, returns `Some(view)`. Otherwise returns `None`.
219 ///
220 /// # Safety
221 ///
222 /// - `sexp` must be a valid R external pointer (EXTPTRSXP)
223 /// - Must be called on R's main thread
224 ///
225 /// # Returns
226 ///
227 /// - `Some(Self)` if the object implements the trait
228 /// - `None` if the object does not implement the trait
229 ///
230 /// # Example
231 ///
232 /// ```ignore
233 /// let counter = unsafe { CounterView::try_from_sexp(obj) }
234 /// .expect("Object does not implement Counter");
235 /// ```
236 #[inline]
237 unsafe fn try_from_sexp(sexp: SEXP) -> Option<Self> {
238 // SAFETY: Caller guarantees sexp is valid and we're on main thread
239 unsafe {
240 // Get the vtable for this trait
241 let vtable = ccall::mx_query(sexp, Self::TAG);
242 if vtable.is_null() {
243 return None;
244 }
245
246 // Get the erased pointer (points to the wrapper struct header)
247 let raw_ptr = crate::ffi::R_ExternalPtrAddr(sexp);
248 if raw_ptr.is_null() {
249 return None;
250 }
251 let erased_ptr = raw_ptr.cast::<crate::abi::mx_erased>();
252
253 // The wrapper struct layout is:
254 // #[repr(C)]
255 // struct __MxWrapper<T> {
256 // erased: mx_erased, // offset 0
257 // data: T, // offset = size_of::<mx_erased>() rounded up to align_of::<T>()
258 // }
259 // When T has stricter alignment than mx_erased, padding exists between
260 // erased and data. The correct offset is stored in the base vtable.
261 let base = (*erased_ptr).base;
262 let data_offset = (*base).data_offset;
263 let data = erased_ptr.cast::<u8>().add(data_offset).cast::<c_void>();
264
265 Some(Self::from_raw_parts(data, vtable))
266 }
267 }
268
269 /// Try to create a view, returning an error message on failure.
270 ///
271 /// Similar to `try_from_sexp` but returns an error string suitable for
272 /// use as an R error message if the object does not implement the trait.
273 ///
274 /// # Safety
275 ///
276 /// Same as `try_from_sexp`.
277 #[inline]
278 unsafe fn try_from_sexp_or_error(sexp: SEXP, trait_name: &str) -> Result<Self, String> {
279 // SAFETY: Delegated to try_from_sexp
280 unsafe {
281 Self::try_from_sexp(sexp)
282 .ok_or_else(|| format!("Object does not implement {} trait", trait_name))
283 }
284 }
285}
286// endregion