Skip to content

Commit b5330c3

Browse files
committed
Adding FfiType::Callback
This type can be used whenever we need to define a callback functions. Rather than adding the type by hand, which was quite annoying when I was doing the async work, we now can just add an item in `ffi_callback_definitions()`. This also can help avoid FFI type mismatches, like how I made the Kotlin signature input a i16 instead of an i8. Use the new type for the future continuation callback. I have another plan for callback interface callbacks. I didn't touch the foreign executor callback, I hope to work on that very soon.
1 parent 63098f9 commit b5330c3

File tree

14 files changed

+155
-36
lines changed

14 files changed

+155
-36
lines changed

uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs

+16-8
Original file line numberDiff line numberDiff line change
@@ -308,14 +308,19 @@ impl KotlinCodeOracle {
308308
nm.to_string().to_shouty_snake_case()
309309
}
310310

311-
fn ffi_type_label_by_value(ffi_type: &FfiType) -> String {
311+
/// Get the idiomatic Python rendering of an FFI callback function
312+
fn ffi_callback_name(&self, nm: &str) -> String {
313+
format!("Uniffi{}", nm.to_upper_camel_case())
314+
}
315+
316+
fn ffi_type_label_by_value(&self, ffi_type: &FfiType) -> String {
312317
match ffi_type {
313-
FfiType::RustBuffer(_) => format!("{}.ByValue", Self::ffi_type_label(ffi_type)),
314-
_ => Self::ffi_type_label(ffi_type),
318+
FfiType::RustBuffer(_) => format!("{}.ByValue", self.ffi_type_label(ffi_type)),
319+
_ => self.ffi_type_label(ffi_type),
315320
}
316321
}
317322

318-
fn ffi_type_label(ffi_type: &FfiType) -> String {
323+
fn ffi_type_label(&self, ffi_type: &FfiType) -> String {
319324
match ffi_type {
320325
// Note that unsigned integers in Kotlin are currently experimental, but java.nio.ByteBuffer does not
321326
// support them yet. Thus, we use the signed variants to represent both signed and unsigned
@@ -331,11 +336,9 @@ impl KotlinCodeOracle {
331336
format!("RustBuffer{}", maybe_suffix.as_deref().unwrap_or_default())
332337
}
333338
FfiType::ForeignBytes => "ForeignBytes.ByValue".to_string(),
339+
FfiType::Callback(name) => self.ffi_callback_name(name),
334340
FfiType::ForeignCallback => "ForeignCallback".to_string(),
335341
FfiType::RustFutureHandle => "Pointer".to_string(),
336-
FfiType::RustFutureContinuationCallback => {
337-
"UniFffiRustFutureContinuationCallbackType".to_string()
338-
}
339342
FfiType::RustFutureContinuationData => "USize".to_string(),
340343
}
341344
}
@@ -472,7 +475,7 @@ mod filters {
472475
}
473476

474477
pub fn ffi_type_name_by_value(type_: &FfiType) -> Result<String, askama::Error> {
475-
Ok(KotlinCodeOracle::ffi_type_label_by_value(type_))
478+
Ok(KotlinCodeOracle.ffi_type_label_by_value(type_))
476479
}
477480

478481
/// Get the idiomatic Kotlin rendering of a function name.
@@ -500,6 +503,11 @@ mod filters {
500503
Ok(KotlinCodeOracle.convert_error_suffix(&name))
501504
}
502505

506+
/// Get the idiomatic Kotlin rendering of an FFI callback function name
507+
pub fn ffi_callback_name(nm: &str) -> Result<String, askama::Error> {
508+
Ok(KotlinCodeOracle.ffi_callback_name(nm))
509+
}
510+
503511
pub fn object_names(
504512
obj: &Object,
505513
ci: &ComponentInterface,

uniffi_bindgen/src/bindings/kotlin/templates/Async.kt

+4-4
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ internal const val UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1.toByte()
66
internal val uniffiContinuationHandleMap = UniFfiHandleMap<CancellableContinuation<Byte>>()
77

88
// FFI type for Rust future continuations
9-
internal object uniffiRustFutureContinuationCallback: UniFffiRustFutureContinuationCallbackType {
10-
override fun callback(continuationHandle: USize, pollResult: Byte) {
11-
uniffiContinuationHandleMap.remove(continuationHandle)?.resume(pollResult)
9+
internal object uniffiRustFutureContinuationCallback: UniffiRustFutureContinuationCallback {
10+
override fun callback(data: USize, pollResult: Byte) {
11+
uniffiContinuationHandleMap.remove(data)?.resume(pollResult)
1212
}
1313
}
1414

1515
internal suspend fun<T, F, E: Exception> uniffiRustCallAsync(
1616
rustFuture: Pointer,
17-
pollFunc: (Pointer, UniFffiRustFutureContinuationCallbackType, USize) -> Unit,
17+
pollFunc: (Pointer, UniffiRustFutureContinuationCallback, USize) -> Unit,
1818
completeFunc: (Pointer, RustCallStatus) -> F,
1919
freeFunc: (Pointer) -> Unit,
2020
liftFunc: (F) -> T,

uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt

-5
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,3 @@ internal class UniFfiHandleMap<T: Any> {
154154
return map.remove(handle)
155155
}
156156
}
157-
158-
// FFI type for Rust future continuations
159-
internal interface UniFffiRustFutureContinuationCallbackType : com.sun.jna.Callback {
160-
fun callback(continuationHandle: USize, pollResult: Byte);
161-
}

uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt

+16
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,22 @@ private inline fun <reified Lib : Library> loadIndirect(
1313
return Native.load<Lib>(findLibraryName(componentName), Lib::class.java)
1414
}
1515

16+
// Define FFI callback types
17+
{%- for callback in ci.ffi_callback_definitions() %}
18+
internal interface {{ callback.name()|ffi_callback_name }} : com.sun.jna.Callback {
19+
fun callback(
20+
{%- for arg in callback.arguments() -%}
21+
{{ arg.name().borrow()|var_name }}: {{ arg.type_().borrow()|ffi_type_name_by_value }},
22+
{%- endfor -%}
23+
)
24+
{%- match callback.return_type() %}
25+
{%- when Some(return_type) %}: {{ return_type|ffi_type_name_by_value }}
26+
{%- when None %}
27+
{%- endmatch %}
28+
}
29+
30+
{%- endfor %}
31+
1632
// A JNA Library to expose the extern-C FFI definitions.
1733
// This is an implementation detail which will be called internally by the public API.
1834

uniffi_bindgen/src/bindings/python/gen_python/mod.rs

+13-3
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,12 @@ impl PythonCodeOracle {
337337
fixup_keyword(nm.to_string().to_shouty_snake_case())
338338
}
339339

340-
fn ffi_type_label(ffi_type: &FfiType) -> String {
340+
/// Get the idiomatic Python rendering of an FFI callback function
341+
fn ffi_callback_name(&self, nm: &str) -> String {
342+
format!("UNIFFI_{}", nm.to_shouty_snake_case())
343+
}
344+
345+
fn ffi_type_label(&self, ffi_type: &FfiType) -> String {
341346
match ffi_type {
342347
FfiType::Int8 => "ctypes.c_int8".to_string(),
343348
FfiType::UInt8 => "ctypes.c_uint8".to_string(),
@@ -355,10 +360,10 @@ impl PythonCodeOracle {
355360
None => "_UniffiRustBuffer".to_string(),
356361
},
357362
FfiType::ForeignBytes => "_UniffiForeignBytes".to_string(),
363+
FfiType::Callback(name) => self.ffi_callback_name(name),
358364
FfiType::ForeignCallback => "_UNIFFI_FOREIGN_CALLBACK_T".to_string(),
359365
// Pointer to an `asyncio.EventLoop` instance
360366
FfiType::RustFutureHandle => "ctypes.c_void_p".to_string(),
361-
FfiType::RustFutureContinuationCallback => "_UNIFFI_FUTURE_CONTINUATION_T".to_string(),
362367
FfiType::RustFutureContinuationData => "ctypes.c_size_t".to_string(),
363368
}
364369
}
@@ -482,7 +487,7 @@ pub mod filters {
482487
}
483488

484489
pub fn ffi_type_name(type_: &FfiType) -> Result<String, askama::Error> {
485-
Ok(PythonCodeOracle::ffi_type_label(type_))
490+
Ok(PythonCodeOracle.ffi_type_label(type_))
486491
}
487492

488493
/// Get the idiomatic Python rendering of a class name (for enums, records, errors, etc).
@@ -505,6 +510,11 @@ pub mod filters {
505510
Ok(PythonCodeOracle.enum_variant_name(nm))
506511
}
507512

513+
/// Get the idiomatic Python rendering of a FFI callback function name
514+
pub fn ffi_callback_name(nm: &str) -> Result<String, askama::Error> {
515+
Ok(PythonCodeOracle.ffi_callback_name(nm))
516+
}
517+
508518
/// Get the idiomatic Python rendering of an individual enum variant.
509519
pub fn object_names(obj: &Object) -> Result<(String, String), askama::Error> {
510520
Ok(PythonCodeOracle.object_names(obj))

uniffi_bindgen/src/bindings/python/templates/Async.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
# Continuation callback for async functions
99
# lift the return value or error and resolve the future, causing the async function to resume.
10-
@_UNIFFI_FUTURE_CONTINUATION_T
10+
@UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK
1111
def _uniffi_continuation_callback(future_ptr, poll_code):
1212
(eventloop, future) = _UniffiContinuationPointerManager.release_pointer(future_ptr)
1313
eventloop.call_soon_threadsafe(_uniffi_set_future_result, future, poll_code)

uniffi_bindgen/src/bindings/python/templates/Helpers.py

-3
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,3 @@ def _uniffi_check_call_status(error_ffi_converter, call_status):
7070
# Rust definition `fn(handle: u64, method: u32, args: _UniffiRustBuffer, buf_ptr: *mut _UniffiRustBuffer) -> int`
7171
_UNIFFI_FOREIGN_CALLBACK_T = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_ulonglong, ctypes.c_ulong, ctypes.POINTER(ctypes.c_char), ctypes.c_int, ctypes.POINTER(_UniffiRustBuffer))
7272

73-
# UniFFI future continuation
74-
_UNIFFI_FUTURE_CONTINUATION_T = ctypes.CFUNCTYPE(None, ctypes.c_size_t, ctypes.c_int8)
75-

uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py

+14
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,20 @@ def _uniffi_check_api_checksums(lib):
5252
pass
5353
{%- endfor %}
5454

55+
56+
# Define FFI callback types
57+
{%- for callback in ci.ffi_callback_definitions() %}
58+
{{ callback.name()|ffi_callback_name }} = ctypes.CFUNCTYPE(
59+
{%- match callback.return_type() %}
60+
{%- when Some(return_type) %}{{ return_type|ffi_type_name }},
61+
{%- when None %}None,
62+
{%- endmatch %}
63+
{%- for arg in callback.arguments() -%}
64+
{{ arg.type_().borrow()|ffi_type_name }},
65+
{%- endfor -%}
66+
)
67+
{%- endfor %}
68+
5569
# A ctypes library to expose the extern-C FFI definitions.
5670
# This is an implementation detail which will be called internally by the public API.
5771

uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,11 @@ mod filters {
152152
FfiType::RustArcPtr(_) => ":pointer".to_string(),
153153
FfiType::RustBuffer(_) => "RustBuffer.by_value".to_string(),
154154
FfiType::ForeignBytes => "ForeignBytes".to_string(),
155+
FfiType::Callback(_) => unimplemented!("FFI Callbacks not implemented"),
155156
// Callback interfaces are not yet implemented, but this needs to return something in
156157
// order for the coverall tests to pass.
157158
FfiType::ForeignCallback => ":pointer".to_string(),
158-
FfiType::RustFutureHandle
159-
| FfiType::RustFutureContinuationCallback
160-
| FfiType::RustFutureContinuationData => {
159+
FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => {
161160
unimplemented!("Async functions are not implemented")
162161
}
163162
})

uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs

+19-2
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,11 @@ impl SwiftCodeOracle {
507507
nm.to_string().to_lower_camel_case()
508508
}
509509

510+
/// Get the idiomatic Python rendering of an FFI callback function
511+
fn ffi_callback_name(&self, nm: &str) -> String {
512+
format!("Uniffi{}", nm.to_upper_camel_case())
513+
}
514+
510515
fn ffi_type_label_raw(&self, ffi_type: &FfiType) -> String {
511516
match ffi_type {
512517
FfiType::Int8 => "Int8".into(),
@@ -522,8 +527,8 @@ impl SwiftCodeOracle {
522527
FfiType::RustArcPtr(_) => "UnsafeMutableRawPointer".into(),
523528
FfiType::RustBuffer(_) => "RustBuffer".into(),
524529
FfiType::ForeignBytes => "ForeignBytes".into(),
530+
FfiType::Callback(name) => self.ffi_callback_name(name),
525531
FfiType::ForeignCallback => "ForeignCallback".into(),
526-
FfiType::RustFutureContinuationCallback => "UniFfiRustFutureContinuation".into(),
527532
FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => {
528533
"UnsafeMutableRawPointer".into()
529534
}
@@ -534,7 +539,6 @@ impl SwiftCodeOracle {
534539
match ffi_type {
535540
FfiType::ForeignCallback
536541
| FfiType::RustFutureHandle
537-
| FfiType::RustFutureContinuationCallback
538542
| FfiType::RustFutureContinuationData => {
539543
format!("{} _Nonnull", self.ffi_type_label_raw(ffi_type))
540544
}
@@ -635,10 +639,18 @@ pub mod filters {
635639
FfiType::RustArcPtr(_) => "void*_Nonnull".into(),
636640
FfiType::RustBuffer(_) => "RustBuffer".into(),
637641
FfiType::ForeignBytes => "ForeignBytes".into(),
642+
FfiType::Callback(name) => {
643+
format!("{} _Nonnull", SwiftCodeOracle.ffi_callback_name(name))
644+
}
638645
FfiType::ForeignCallback => "ForeignCallback _Nonnull".into(),
646+
<<<<<<< HEAD
639647
FfiType::RustFutureContinuationCallback => {
640648
"UniFfiRustFutureContinuation _Nonnull".into()
641649
}
650+
=======
651+
FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback _Nonnull".into(),
652+
FfiType::ForeignExecutorHandle => "size_t".into(),
653+
>>>>>>> 1f20f993c (Adding `FfiType::Callback`)
642654
FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => {
643655
"void* _Nonnull".into()
644656
}
@@ -676,6 +688,11 @@ pub mod filters {
676688
Ok(oracle().enum_variant_name(nm))
677689
}
678690

691+
/// Get the idiomatic Swift rendering of an ffi callback function name
692+
pub fn ffi_callback_name(nm: &str) -> Result<String, askama::Error> {
693+
Ok(oracle().ffi_callback_name(nm))
694+
}
695+
679696
/// Get the idiomatic Swift rendering of docstring
680697
pub fn docstring(docstring: &str, spaces: &i32) -> Result<String, askama::Error> {
681698
let middle = textwrap::indent(&textwrap::dedent(docstring), " * ");

uniffi_bindgen/src/bindings/swift/templates/Async.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ private let UNIFFI_RUST_FUTURE_POLL_MAYBE_READY: Int8 = 1
33

44
fileprivate func uniffiRustCallAsync<F, T>(
55
rustFutureFunc: () -> UnsafeMutableRawPointer,
6-
pollFunc: (UnsafeMutableRawPointer, @escaping UniFfiRustFutureContinuation, UnsafeMutableRawPointer) -> (),
6+
pollFunc: (UnsafeMutableRawPointer, @escaping UniffiRustFutureContinuationCallback, UnsafeMutableRawPointer) -> (),
77
completeFunc: (UnsafeMutableRawPointer, UnsafeMutablePointer<RustCallStatus>) -> F,
88
freeFunc: (UnsafeMutableRawPointer) -> (),
99
liftFunc: (F) throws -> T,

uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h

+11-2
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,17 @@ typedef struct RustCallStatus {
4747
// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️
4848
#endif // def UNIFFI_SHARED_H
4949

50-
// Continuation callback for UniFFI Futures
51-
typedef void (*UniFfiRustFutureContinuation)(void * _Nonnull, int8_t);
50+
// Define FFI callback types
51+
{%- for callback in ci.ffi_callback_definitions() %}
52+
typedef
53+
{%- match callback.return_type() %}{% when Some(return_type) %} {{ return_type|ffi_type_name }} {% when None %} void {% endmatch -%}
54+
(*{{ callback.name()|ffi_callback_name }})(
55+
{%- for arg in callback.arguments() -%}
56+
{{ arg.type_().borrow()|header_ffi_type_name }}
57+
{%- if !loop.last %}, {% endif %}
58+
{%- endfor -%}
59+
);
60+
{%- endfor %}
5261

5362
// Scaffolding functions
5463
{%- for func in ci.iter_ffi_function_definitions() %}

uniffi_bindgen/src/interface/ffi.rs

+41-2
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,13 @@ pub enum FfiType {
4747
/// A borrowed reference to some raw bytes owned by foreign language code.
4848
/// The provider of this reference must keep it alive for the duration of the receiving call.
4949
ForeignBytes,
50+
/// Pointer to a callback function. The inner type is the name of the callback, which matches
51+
/// one of the items in [crate::ComponentInterface::ffi_callback_definitions].
52+
Callback(String),
5053
/// Pointer to a callback function that handles all callbacks on the foreign language side.
5154
ForeignCallback,
5255
/// Pointer to a Rust future
5356
RustFutureHandle,
54-
/// Continuation function for a Rust future
55-
RustFutureContinuationCallback,
5657
RustFutureContinuationData,
5758
// TODO: you can imagine a richer structural typesystem here, e.g. `Ref<String>` or something.
5859
// We don't need that yet and it's possible we never will, so it isn't here for now.
@@ -224,14 +225,52 @@ pub struct FfiArgument {
224225
}
225226

226227
impl FfiArgument {
228+
pub fn new(name: impl Into<String>, type_: FfiType) -> Self {
229+
Self {
230+
name: name.into(),
231+
type_,
232+
}
233+
}
234+
227235
pub fn name(&self) -> &str {
228236
&self.name
229237
}
238+
230239
pub fn type_(&self) -> FfiType {
231240
self.type_.clone()
232241
}
233242
}
234243

244+
/// Represents an "extern C"-style callback function
245+
///
246+
/// These are defined in the foreign code and passed to Rust as a function pointer.
247+
#[derive(Debug, Default, Clone)]
248+
pub struct FfiCallbackFunction {
249+
// Name for this function type. This matches the value inside `FfiType::Callback`
250+
pub(super) name: String,
251+
pub(super) arguments: Vec<FfiArgument>,
252+
pub(super) return_type: Option<FfiType>,
253+
pub(super) has_rust_call_status_arg: bool,
254+
}
255+
256+
impl FfiCallbackFunction {
257+
pub fn name(&self) -> &str {
258+
&self.name
259+
}
260+
261+
pub fn arguments(&self) -> Vec<&FfiArgument> {
262+
self.arguments.iter().collect()
263+
}
264+
265+
pub fn return_type(&self) -> Option<&FfiType> {
266+
self.return_type.as_ref()
267+
}
268+
269+
pub fn has_rust_call_status_arg(&self) -> bool {
270+
self.has_rust_call_status_arg
271+
}
272+
}
273+
235274
#[cfg(test)]
236275
mod test {
237276
// There's not really much to test here to be honest,

0 commit comments

Comments
 (0)