interoptopus/patterns/
callbacks.rs

1//! Useful when `extern "C" fn()` delegate types give compile errors.
2//!
3//! # Example
4//!
5//! If you want to accept user-provided callbacks or "delegates":
6//!
7//!```
8//! use interoptopus::{ffi_function, callback};
9//! use interoptopus::patterns::slice::FFISlice;
10//!
11//! callback!(CallbackSlice(x: FFISlice<u8>) -> u8);
12//!
13//! #[ffi_function]
14//! pub extern "C" fn my_function(callback: CallbackSlice) {
15//!     callback.call(FFISlice::empty());
16//! }
17//!
18//! ```
19//! Backends supporting this pattern might generate the equivalent to the following pseudo-code:
20//!
21//! ```csharp
22//! public delegate uint CallbackSlice(Sliceu8 x0);
23//!
24//! void my_function(CallbackSlice callback);
25//! ```
26//!
27//! Backends not supporting this pattern, and C FFI, will see the equivalent of the following C code:
28//! ```c
29//! typedef void (*fptr_fn_Sliceu8)(my_library_slicemutu8 x0);
30//!
31//! void my_function(fptr_fn_Sliceu8 callback);
32//! ```
33//!
34//!
35//! # Code Generation
36//!
37//! The macro [**`callback`**](crate::callback) enables two use cases:
38//!
39//! - On the **Rust** side it will generate a new function-pointer type with better compatibility
40//! with respect to lifetimes in signatures, and accepting an unlimited number of args.
41//!- On the **FFI side** a _properly named_ callback (delegate, function pointer ...) type can be
42//! produced (e.g., `MyCallback`), instead of one where it's name is just a generic concatenation
43//! of all used parameters (e.g., `InteropDelegate_fn_i32_i32`).
44//!
45//!
46//! # Background
47//!
48//! Due to how we generate FFI metadata and how Rust traits work there are some types which
49//! don't work nicely with Interoptopus: function pointers. Practically speaking, the following code _should_ work:
50//!
51//! ```ignore
52//! use interoptopus::ffi_function;
53//! use interoptopus::patterns::slice::FFISlice;
54//!
55//! pub type CallbackSlice = extern "C" fn(FFISlice<u8>);
56//!
57//! #[ffi_function]
58//! pub extern "C" fn my_function(callback: CallbackSlice) {
59//!     callback(FFISlice::empty());
60//! }
61//!
62//! ```
63//!
64//! The intention is to provide a FFI function `my_function`, accepting
65//! a callback, which in turn accepts an `FFISlice<'a, u8>`.
66//! Although this is valid FFI code to write, a compile error is returned, which may look like this:
67//!
68//! ```text
69//! error: implementation of `CTypeInfo` is not general enough
70//!    [...]
71//!    = note: ...`CTypeInfo` would have to be implemented for the type `for<'r> extern "C" fn(FFISlice<'r, u8>)`
72//!    = note: ...but `CTypeInfo` is actually implemented for the type `extern "C" fn(FFISlice<'0, u8>)`, for some specific lifetime `'0`
73//!    = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
74//! ```
75//!
76//! The reasons for this are somewhat technical, but it boils down to us being unable to generally
77//! implement [`CTypeInfo`](crate::lang::rust::CTypeInfo) for _all_ types you may want to use;
78//! [`FFISlice`](crate::patterns::slice::FFISlice) here being one of them.
79//! To fix this, you can replace `pub type CallbackSlice = ...` with a `callback!` call
80//! which should generate a helper type that works.
81//!
82
83use crate::lang::c::{FnPointerType, Meta};
84
85/// Internal helper naming a generated callback type wrapper.
86#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
87pub struct NamedCallback {
88    fnpointer: FnPointerType,
89    meta: Meta,
90}
91
92impl NamedCallback {
93    /// Creates a new named callback.
94    pub fn new(callback: FnPointerType) -> Self {
95        Self::with_meta(callback, Meta::new())
96    }
97
98    /// Creates a new named callback with the given meta.
99    pub fn with_meta(callback: FnPointerType, meta: Meta) -> Self {
100        if let None = callback.name() {
101            panic!("The pointer provided to a named callback must have a name.")
102        }
103        Self { fnpointer: callback, meta }
104    }
105
106    /// Gets the type name of this callback.
107    pub fn name(&self) -> &str {
108        &self.fnpointer.name().unwrap()
109    }
110
111    /// Gets the type's meta.
112    pub fn meta(&self) -> &Meta {
113        &self.meta
114    }
115
116    /// Returns the function pointer type.
117    pub fn fnpointer(&self) -> &FnPointerType {
118        &self.fnpointer
119    }
120}
121
122/// Defines a callback type, akin to a `fn f(T) -> R` wrapped in an [Option](std::option).
123///
124/// A named delegate will be emitted in languages supporting them, otherwise a regular
125/// function pointer. For details, please see the [**callbacks module**](crate::patterns::callbacks).
126///
127/// # Example
128///
129/// This defines a type `MyCallback` with a parameter `slice` returning an `u8`.
130///
131/// ```
132/// use interoptopus::callback;
133/// use interoptopus::patterns::slice::FFISlice;
134///
135/// callback!(MyCallback(slice: FFISlice<u8>) -> u8);
136/// ```
137///
138/// The generated type definition similar to:
139///
140/// ```
141/// # use interoptopus::patterns::slice::FFISlice;
142/// #[repr(transparent)]
143/// pub struct MyCallback(Option<extern "C" fn(FFISlice<u8>) -> u8>);
144/// ```
145///
146/// You can also create the callback from Rust for testing:
147///
148/// ```
149/// use interoptopus::callback;
150///
151/// callback!(MyCallback() -> u8);
152///
153/// extern "C" fn my_rust_callback() -> u8 {
154///     42
155/// }
156///
157/// let callback = MyCallback::new(my_rust_callback);
158/// assert_eq!(42, callback.call());
159/// ```
160#[macro_export]
161macro_rules! callback {
162    ($name:ident($($param:ident: $ty:ty),*)) => {
163        callback!($name($($param: $ty),*) -> ());
164    };
165    ($name:ident($($param:ident: $ty:ty),*) -> $rval:ty $(, namespace = $ns:expr)?) => {
166        #[derive(Default, Clone)]
167        #[repr(transparent)]
168        pub struct $name(Option<extern "C" fn($($ty),*) -> $rval>);
169
170        impl $name {
171            /// Creates a new instance of the callback using `extern "C" fn`
172            pub fn new(func: extern "C" fn($($ty),*) -> $rval) -> Self {
173                Self(Some(func))
174            }
175
176            /// Will call function if it exists, panic otherwise.
177            pub fn call(&self, $($param: $ty),*) -> $rval {
178                self.0.expect("Assumed function would exist but it didn't.")($($param),*)
179            }
180
181            /// Will call function only if it exists
182            pub fn call_if_some(&self, $($param: $ty),*) -> Option<$rval> {
183                match self.0 {
184                    Some(c) => Some(c($($param),*)),
185                    None => None
186                }
187            }
188        }
189
190        impl From<extern "C" fn($($ty),*) -> $rval> for $name {
191            fn from(x: extern "C" fn($($ty),*) -> $rval) -> Self {
192                Self(Some(x))
193            }
194        }
195
196        impl From<$name> for Option<extern "C" fn($($ty),*) -> $rval> {
197            fn from(x: $name) -> Self {
198                x.0
199            }
200        }
201
202        unsafe impl interoptopus::lang::rust::CTypeInfo for $name {
203            fn type_info() -> interoptopus::lang::c::CType {
204                use interoptopus::lang::rust::CTypeInfo;
205                use interoptopus::lang::c::{Meta, Documentation};
206
207                let rval = < $rval as CTypeInfo >::type_info();
208
209                let params = vec![
210                $(
211                    interoptopus::lang::c::Parameter::new(stringify!($param).to_string(), < $ty as CTypeInfo >::type_info()),
212                )*
213                ];
214
215                let mut namespace = String::new();
216                $(
217                    namespace = String::from($ns);
218                )*
219
220                let meta = Meta::with_namespace_documentation(namespace, Documentation::new(), None);
221
222                let sig = interoptopus::lang::c::FunctionSignature::new(params, rval);
223                let fn_pointer = interoptopus::lang::c::FnPointerType::new_named(sig, stringify!($name).to_string());
224                let named_callback = interoptopus::patterns::callbacks::NamedCallback::with_meta(fn_pointer, meta);
225
226                interoptopus::lang::c::CType::Pattern(interoptopus::patterns::TypePattern::NamedCallback(named_callback))
227            }
228        }
229    };
230}