Skip to content

Commit 41df0c3

Browse files
committed
Async naming updates and doc fixes
After implementing foreign futures these in JS, I wanted to make some updates. The FFI is staying the same, but some names have been changed. `UniffiForeignFutureFree` is now named `UniffiForeignFutureDroppedCallback` and `UniffiForeignFuture` is now `UniffiForeignFutureDroppedCallbackStruct`. This reflects the fact that we don't really need to use these to manage the future, the real reason they're here nowadays is to support cancellation. For languages like JS, where we can't implement cancellation, we can just ignore these structs and the param. Updated some of the code that deals with this to treat it like the out parameter it is, rather than a return value. I think that was leftover from previous implementations. There's also one change for RustFuture: `RustFuturePoll::MaybeReady` is now `RustFuturePoll::Wake`. "MaybeReady" was always a confusing/ambiguous name. "Wake" seems reasonable, since it's what the foreign bindings should do and reflects how the Rust futures code works. Updated some outdated docstrings.
1 parent 9b78d2b commit 41df0c3

File tree

16 files changed

+154
-114
lines changed

16 files changed

+154
-114
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@
1818

1919
[All changes in [[UnreleasedUniFFIVersion]]](https://github.com/mozilla/uniffi-rs/compare/v0.29.1...HEAD).
2020

21+
### ⚠️ Breaking Changes for external bindings authors ⚠️
22+
23+
* Some async-related names have changed. Bindings authors may need to update their code to reflect
24+
the new names. This is a name-change only -- the FFI and semantics are still the same.
25+
26+
* `UniffiForeignFutureFree` is now `UniffiForeignFutureDroppedCallback`
27+
* `UniffiForeignFuture` is `UniffiForeignFutureDroppedCallbackStruct`.
28+
* `RustFuturePoll::MaybeReady` is now `RustFuturePoll::Wake`.
29+
2130
## v0.29.1 (backend crates: v0.29.1) - (_2025-03-18_)
2231

2332
### What's fixed?

docs/manual/src/internals/async-overview.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,8 @@ The UniFFI generated code for an async function performs these steps:
5151

5252
1. Call the Rust scaffolding function, receiving a `RustFuture` handle
5353
1. Call the `rust_future_poll` function for the future until the future is ready.
54-
* The `rust_future_poll` function inputs a callback function and an opaque pointer (AKA a `void *`) to call the callback with.
55-
* If the future is pending, then the generated code registers a waker that will call the callback function with `RUST_FUTURE_MAYBE_READY`.
56-
When the generated foreign code sees this, it calls poll again, starting the loop over.
54+
* The `rust_future_poll` function inputs a callback function and a `u64` callback data value to pass to the callback.
55+
* If the future is pending, then the generated code registers a waker that will call the callback function with `RUST_FUTURE_WAKE`.
5756
* If the future is ready, then the callback function is immediately called with `RUST_FUTURE_READY` and we move to the next step.
5857
1. Call `rust_future_complete`, receiving the return value of the future
5958
1. Call `rust_future_free` (ideally in a `finally` block)

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Async return type handlers
22

33
internal const val UNIFFI_RUST_FUTURE_POLL_READY = 0.toByte()
4-
internal const val UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1.toByte()
4+
internal const val UNIFFI_RUST_FUTURE_POLL_WAKE = 1.toByte()
55

66
internal val uniffiContinuationHandleMap = UniffiHandleMap<CancellableContinuation<Byte>>()
77

@@ -44,12 +44,13 @@ internal inline fun<T> uniffiTraitInterfaceCallAsync(
4444
crossinline makeCall: suspend () -> T,
4545
crossinline handleSuccess: (T) -> Unit,
4646
crossinline handleError: (UniffiRustCallStatus.ByValue) -> Unit,
47-
): UniffiForeignFuture {
47+
uniffiOutDroppedCallback: UniffiForeignFutureDroppedCallbackStruct,
48+
) {
4849
// Using `GlobalScope` is labeled as a "delicate API" and generally discouraged in Kotlin programs, since it breaks structured concurrency.
4950
// However, our parent task is a Rust future, so we're going to need to break structure concurrency in any case.
5051
//
5152
// Uniffi does its best to support structured concurrency across the FFI.
52-
// If the Rust future is dropped, `uniffiForeignFutureFreeImpl` is called, which will cancel the Kotlin coroutine if it's still running.
53+
// If the Rust future is dropped, `uniffiForeignFutureDroppedCallbackImpl` is called, which will cancel the Kotlin coroutine if it's still running.
5354
@OptIn(DelicateCoroutinesApi::class)
5455
val job = GlobalScope.launch {
5556
try {
@@ -64,15 +65,16 @@ internal inline fun<T> uniffiTraitInterfaceCallAsync(
6465
}
6566
}
6667
val handle = uniffiForeignFutureHandleMap.insert(job)
67-
return UniffiForeignFuture(handle, uniffiForeignFutureFreeImpl)
68+
uniffiOutDroppedCallback.uniffiSetValue(UniffiForeignFutureDroppedCallbackStruct(handle, uniffiForeignFutureDroppedCallbackImpl))
6869
}
6970

7071
internal inline fun<T, reified E: Throwable> uniffiTraitInterfaceCallAsyncWithError(
7172
crossinline makeCall: suspend () -> T,
7273
crossinline handleSuccess: (T) -> Unit,
7374
crossinline handleError: (UniffiRustCallStatus.ByValue) -> Unit,
7475
crossinline lowerError: (E) -> RustBuffer.ByValue,
75-
): UniffiForeignFuture {
76+
uniffiOutDroppedCallback: UniffiForeignFutureDroppedCallbackStruct,
77+
) {
7678
// See uniffiTraitInterfaceCallAsync for details on `DelicateCoroutinesApi`
7779
@OptIn(DelicateCoroutinesApi::class)
7880
val job = GlobalScope.launch {
@@ -97,12 +99,12 @@ internal inline fun<T, reified E: Throwable> uniffiTraitInterfaceCallAsyncWithEr
9799
}
98100
}
99101
val handle = uniffiForeignFutureHandleMap.insert(job)
100-
return UniffiForeignFuture(handle, uniffiForeignFutureFreeImpl)
102+
uniffiOutDroppedCallback.uniffiSetValue(UniffiForeignFutureDroppedCallbackStruct(handle, uniffiForeignFutureDroppedCallbackImpl))
101103
}
102104

103105
internal val uniffiForeignFutureHandleMap = UniffiHandleMap<Job>()
104106

105-
internal object uniffiForeignFutureFreeImpl: UniffiForeignFutureFree {
107+
internal object uniffiForeignFutureDroppedCallbackImpl: UniffiForeignFutureDroppedCallback {
106108
override fun callback(handle: Long) {
107109
val job = uniffiForeignFutureHandleMap.remove(handle)
108110
if (!job.isCompleted) {

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

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -67,23 +67,23 @@ internal object {{ trait_impl }} {
6767
)
6868
}
6969

70-
uniffiOutReturn.uniffiSetValue(
71-
{%- match meth.throws_type() %}
72-
{%- when None %}
73-
uniffiTraitInterfaceCallAsync(
74-
makeCall,
75-
uniffiHandleSuccess,
76-
uniffiHandleError
77-
)
78-
{%- when Some(error_type) %}
79-
uniffiTraitInterfaceCallAsyncWithError(
80-
makeCall,
81-
uniffiHandleSuccess,
82-
uniffiHandleError,
83-
{ e: {{error_type|type_name(ci) }} -> {{ error_type|lower_fn }}(e) }
84-
)
85-
{%- endmatch %}
70+
{%- match meth.throws_type() %}
71+
{%- when None %}
72+
uniffiTraitInterfaceCallAsync(
73+
makeCall,
74+
uniffiHandleSuccess,
75+
uniffiHandleError,
76+
uniffiOutDroppedCallback
8677
)
78+
{%- when Some(error_type) %}
79+
uniffiTraitInterfaceCallAsyncWithError(
80+
makeCall,
81+
uniffiHandleSuccess,
82+
uniffiHandleError,
83+
{ e: {{error_type|type_name(ci) }} -> {{ error_type|lower_fn }}(e) },
84+
uniffiOutDroppedCallback
85+
)
86+
{%- endmatch %}
8787
{%- endif %}
8888
}
8989
}

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# RustFuturePoll values
22
_UNIFFI_RUST_FUTURE_POLL_READY = 0
3-
_UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1
3+
_UNIFFI_RUST_FUTURE_POLL_WAKE = 1
44

55
# Stores futures for _uniffi_continuation_callback
66
_UniffiContinuationHandleMap = _UniffiHandleMap()
@@ -63,7 +63,7 @@ async def _uniffi_rust_call_async(rust_future, ffi_poll, ffi_complete, ffi_free,
6363
ffi_free(rust_future)
6464

6565
{%- if ci.has_async_callback_interface_definition() %}
66-
def _uniffi_trait_interface_call_async(make_call, handle_success, handle_error):
66+
def _uniffi_trait_interface_call_async(make_call, uniffi_out_dropped_callback, handle_success, handle_error):
6767
async def make_call_and_call_callback():
6868
try:
6969
handle_success(await make_call())
@@ -77,9 +77,9 @@ async def make_call_and_call_callback():
7777
eventloop = _uniffi_get_event_loop()
7878
task = asyncio.run_coroutine_threadsafe(make_call_and_call_callback(), eventloop)
7979
handle = _UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.insert((eventloop, task))
80-
return _UniffiForeignFuture(handle, _uniffi_foreign_future_free)
80+
uniffi_out_dropped_callback[0] = _UniffiForeignFutureDroppedCallbackStruct(handle, _uniffi_future_dropped_callback)
8181

82-
def _uniffi_trait_interface_call_async_with_error(make_call, handle_success, handle_error, error_type, lower_error):
82+
def _uniffi_trait_interface_call_async_with_error(make_call, uniffi_out_dropped_callback, handle_success, handle_error, error_type, lower_error):
8383
async def make_call_and_call_callback():
8484
try:
8585
try:
@@ -99,16 +99,16 @@ async def make_call_and_call_callback():
9999
eventloop = _uniffi_get_event_loop()
100100
task = asyncio.run_coroutine_threadsafe(make_call_and_call_callback(), eventloop)
101101
handle = _UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.insert((eventloop, task))
102-
return _UniffiForeignFuture(handle, _uniffi_foreign_future_free)
102+
uniffi_out_dropped_callback[0] = _UniffiForeignFutureDroppedCallbackStruct(handle, _uniffi_future_dropped_callback)
103103

104104
_UNIFFI_FOREIGN_FUTURE_HANDLE_MAP = _UniffiHandleMap()
105105

106-
@_UNIFFI_FOREIGN_FUTURE_FREE
107-
def _uniffi_foreign_future_free(handle):
106+
@_UNIFFI_FOREIGN_FUTURE_DROPPED_CALLBACK
107+
def _uniffi_future_dropped_callback(handle):
108108
(eventloop, task) = _UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.remove(handle)
109-
eventloop.call_soon(_uniffi_foreign_future_do_free, task)
109+
eventloop.call_soon(_uniffi_cancel_task, task)
110110

111-
def _uniffi_foreign_future_do_free(task):
111+
def _uniffi_cancel_task(task):
112112
if not task.done():
113113
task.cancel()
114114
{%- endif %}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ def handle_error(status_code, rust_buffer):
7272

7373
{%- match meth.throws_type() %}
7474
{%- when None %}
75-
uniffi_out_return[0] = _uniffi_trait_interface_call_async(make_call, handle_success, handle_error)
75+
_uniffi_trait_interface_call_async(make_call, uniffi_out_dropped_callback, handle_success, handle_error)
7676
{%- when Some(error) %}
77-
uniffi_out_return[0] = _uniffi_trait_interface_call_async_with_error(make_call, handle_success, handle_error, {{ error|type_name }}, {{ error|lower_fn }})
77+
_uniffi_trait_interface_call_async_with_error(make_call, uniffi_out_dropped_callback, handle_success, handle_error, {{ error|type_name }}, {{ error|lower_fn }})
7878
{%- endmatch %}
7979
{%- endif %}
8080
{%- endfor %}

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

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
private let UNIFFI_RUST_FUTURE_POLL_READY: Int8 = 0
2-
private let UNIFFI_RUST_FUTURE_POLL_MAYBE_READY: Int8 = 1
2+
private let UNIFFI_RUST_FUTURE_POLL_WAKE: Int8 = 1
33

44
fileprivate let uniffiContinuationHandleMap = UniffiHandleMap<UnsafeContinuation<Int8, Never>>()
55

@@ -49,8 +49,9 @@ fileprivate func uniffiFutureContinuationCallback(handle: UInt64, pollResult: In
4949
private func uniffiTraitInterfaceCallAsync<T>(
5050
makeCall: @escaping () async throws -> T,
5151
handleSuccess: @escaping (T) -> (),
52-
handleError: @escaping (Int8, RustBuffer) -> ()
53-
) -> UniffiForeignFuture {
52+
handleError: @escaping (Int8, RustBuffer) -> (),
53+
droppedCallback: UnsafeMutablePointer<UniffiForeignFutureDroppedCallbackStruct>
54+
) {
5455
let task = Task {
5556
do {
5657
handleSuccess(try await makeCall())
@@ -59,16 +60,19 @@ private func uniffiTraitInterfaceCallAsync<T>(
5960
}
6061
}
6162
let handle = UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.insert(obj: task)
62-
return UniffiForeignFuture(handle: handle, free: uniffiForeignFutureFree)
63-
63+
droppedCallback.pointee = UniffiForeignFutureDroppedCallbackStruct(
64+
handle: handle,
65+
free: uniffiForeignFutureDroppedCallback
66+
)
6467
}
6568

6669
private func uniffiTraitInterfaceCallAsyncWithError<T, E>(
6770
makeCall: @escaping () async throws -> T,
6871
handleSuccess: @escaping (T) -> (),
6972
handleError: @escaping (Int8, RustBuffer) -> (),
70-
lowerError: @escaping (E) -> RustBuffer
71-
) -> UniffiForeignFuture {
73+
lowerError: @escaping (E) -> RustBuffer,
74+
droppedCallback: UnsafeMutablePointer<UniffiForeignFutureDroppedCallbackStruct>
75+
) {
7276
let task = Task {
7377
do {
7478
handleSuccess(try await makeCall())
@@ -79,7 +83,10 @@ private func uniffiTraitInterfaceCallAsyncWithError<T, E>(
7983
}
8084
}
8185
let handle = UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.insert(obj: task)
82-
return UniffiForeignFuture(handle: handle, free: uniffiForeignFutureFree)
86+
droppedCallback.pointee = UniffiForeignFutureDroppedCallbackStruct(
87+
handle: handle,
88+
free: uniffiForeignFutureDroppedCallback
89+
)
8390
}
8491

8592
// Borrow the callback handle map implementation to store foreign future handles
@@ -96,15 +103,15 @@ fileprivate protocol UniffiForeignFutureTask {
96103

97104
extension Task: UniffiForeignFutureTask {}
98105

99-
private func uniffiForeignFutureFree(handle: UInt64) {
106+
private func uniffiForeignFutureDroppedCallback(handle: UInt64) {
100107
do {
101108
let task = try UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.remove(handle: handle)
102109
// Set the cancellation flag on the task. If it's still running, the code can check the
103110
// cancellation flag or call `Task.checkCancellation()`. If the task has completed, this is
104111
// a no-op.
105112
task.cancel()
106113
} catch {
107-
print("uniffiForeignFutureFree: handle missing from handlemap")
114+
print("uniffiForeignFutureDroppedCallback: handle missing from handlemap")
108115
}
109116
}
110117

uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceImpl.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,20 +80,21 @@ fileprivate struct {{ trait_impl }} {
8080

8181
{%- match meth.throws_type() %}
8282
{%- when None %}
83-
let uniffiForeignFuture = uniffiTraitInterfaceCallAsync(
83+
uniffiTraitInterfaceCallAsync(
8484
makeCall: makeCall,
8585
handleSuccess: uniffiHandleSuccess,
86-
handleError: uniffiHandleError
86+
handleError: uniffiHandleError,
87+
droppedCallback: uniffiOutDroppedCallback
8788
)
8889
{%- when Some(error_type) %}
89-
let uniffiForeignFuture = uniffiTraitInterfaceCallAsyncWithError(
90+
uniffiTraitInterfaceCallAsyncWithError(
9091
makeCall: makeCall,
9192
handleSuccess: uniffiHandleSuccess,
9293
handleError: uniffiHandleError,
93-
lowerError: {{ error_type|lower_fn }}
94+
lowerError: {{ error_type|lower_fn }},
95+
droppedCallback: uniffiOutDroppedCallback
9496
)
9597
{%- endmatch %}
96-
uniffiOutReturn.pointee = uniffiForeignFuture
9798
{%- endif %}
9899
},
99100
{%- endfor %}

uniffi_bindgen/src/interface/callbacks.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,9 @@ pub fn method_ffi_callback(trait_name: &str, method: &Method, index: usize) -> F
180180
),
181181
FfiArgument::new("uniffi_callback_data", FfiType::UInt64),
182182
FfiArgument::new(
183-
"uniffi_out_return",
184-
FfiType::Struct("ForeignFuture".to_owned()).mut_reference(),
183+
"uniffi_out_dropped_callback",
184+
FfiType::Struct("ForeignFutureDroppedCallbackStruct".to_owned())
185+
.mut_reference(),
185186
),
186187
])
187188
.collect(),
@@ -196,7 +197,7 @@ pub fn foreign_future_ffi_result_struct(return_ffi_type: Option<FfiType>) -> Ffi
196197
let return_type_name =
197198
FfiType::return_type_name(return_ffi_type.as_ref()).to_upper_camel_case();
198199
FfiStruct {
199-
name: format!("ForeignFutureStruct{return_type_name}"),
200+
name: format!("ForeignFutureResult{return_type_name}"),
200201
fields: match return_ffi_type {
201202
Some(return_ffi_type) => vec![
202203
FfiField::new("return_value", return_ffi_type),
@@ -222,7 +223,7 @@ pub fn ffi_foreign_future_complete(return_ffi_type: Option<FfiType>) -> FfiCallb
222223
FfiArgument::new("callback_data", FfiType::UInt64),
223224
FfiArgument::new(
224225
"result",
225-
FfiType::Struct(format!("ForeignFutureStruct{return_type_name}")),
226+
FfiType::Struct(format!("ForeignFutureResult{return_type_name}")),
226227
),
227228
],
228229
return_type: None,

uniffi_bindgen/src/interface/mod.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -658,7 +658,7 @@ impl ComponentInterface {
658658
}
659659
.into(),
660660
FfiCallbackFunction {
661-
name: "ForeignFutureFree".to_owned(),
661+
name: "ForeignFutureDroppedCallback".to_owned(),
662662
arguments: vec![FfiArgument::new("handle", FfiType::UInt64)],
663663
return_type: None,
664664
has_rust_call_status_arg: false,
@@ -672,10 +672,13 @@ impl ComponentInterface {
672672
}
673673
.into(),
674674
FfiStruct {
675-
name: "ForeignFuture".to_owned(),
675+
name: "ForeignFutureDroppedCallbackStruct".to_owned(),
676676
fields: vec![
677677
FfiField::new("handle", FfiType::UInt64),
678-
FfiField::new("free", FfiType::Callback("ForeignFutureFree".to_owned())),
678+
FfiField::new(
679+
"free",
680+
FfiType::Callback("ForeignFutureDroppedCallback".to_owned()),
681+
),
679682
],
680683
}
681684
.into(),

uniffi_core/src/ffi/ffidefault.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,6 @@ impl FfiDefault for crate::RustBuffer {
5353
}
5454
}
5555

56-
impl FfiDefault for crate::ForeignFuture {
57-
fn ffi_default() -> Self {
58-
extern "C" fn free(_handle: u64) {}
59-
crate::ForeignFuture { handle: 0, free }
60-
}
61-
}
62-
6356
impl<T> FfiDefault for Option<T> {
6457
fn ffi_default() -> Self {
6558
None

0 commit comments

Comments
 (0)