Skip to main content

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