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}