From a4fb942eab5ceb0eb61fd3b63563efee111bfaf9 Mon Sep 17 00:00:00 2001 From: Ben Dean-Kawamura Date: Thu, 19 Oct 2023 20:57:46 -0400 Subject: [PATCH] Use Handle for passing Rust objects across the FFI * Added the `SlabAlloc` FFI trait. This is used to manage handles for `Arc` instances, including `Arc` * Also use handles for trait interfaces. This still needs some updates on the foreign side before it's working. * Renamed a bunch of stuff and replaced a lot of comment text * Added the `const_random` crate to generate random slab IDs. This should be a pretty lightweight dpeendency. * Bumped `CONTRACT_VERSION` since this is a change to the FFI --- CHANGELOG.md | 2 + Cargo.lock | 44 ++++++ .../src/bindings/kotlin/gen_kotlin/mod.rs | 3 +- .../src/bindings/kotlin/templates/Async.kt | 8 +- .../src/bindings/kotlin/templates/Helpers.kt | 2 + .../kotlin/templates/ObjectRuntime.kt | 26 ++-- .../kotlin/templates/ObjectTemplate.kt | 32 ++--- .../src/bindings/python/gen_python/mod.rs | 3 +- .../python/templates/ObjectTemplate.py | 28 ++-- .../src/bindings/python/templates/macros.py | 6 +- .../src/bindings/ruby/gen_ruby/mod.rs | 6 +- .../bindings/ruby/templates/ObjectTemplate.rb | 31 +++-- .../ruby/templates/RustBufferBuilder.rb | 4 +- .../ruby/templates/RustBufferStream.rb | 4 +- .../src/bindings/swift/gen_swift/mod.rs | 18 +-- .../src/bindings/swift/templates/Async.swift | 8 +- .../swift/templates/ObjectTemplate.swift | 47 +++---- uniffi_bindgen/src/interface/ffi.rs | 17 +-- uniffi_bindgen/src/interface/mod.rs | 14 +- uniffi_bindgen/src/interface/object.rs | 4 +- uniffi_core/Cargo.toml | 1 + uniffi_core/src/ffi/ffidefault.rs | 6 + uniffi_core/src/ffi/rustfuture.rs | 125 ++++++++++-------- uniffi_core/src/ffi/slab.rs | 16 +++ uniffi_core/src/ffi_converter_traits.rs | 43 +++++- uniffi_core/src/lib.rs | 2 + uniffi_macros/src/export/scaffolding.rs | 6 +- uniffi_macros/src/export/trait_interface.rs | 27 ++-- uniffi_macros/src/object.rs | 34 +++-- uniffi_macros/src/setup_scaffolding.rs | 21 +-- uniffi_macros/src/util.rs | 8 +- 31 files changed, 352 insertions(+), 244 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a8482ae49..1cbe29a60e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ - The `rust_future_continuation_callback_set` FFI function was removed. `rust_future_poll` now inputs the callback pointer. External bindings authors will need to update their code. +- The object handle FFI has changed. External bindings generators will need to update their code to + use the new slab/handle system. ## v0.25.0 (backend crates: v0.25.0) - (_2023-10-18_) diff --git a/Cargo.lock b/Cargo.lock index d23a1e28e2..f684928ee2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -476,6 +476,28 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-random" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368a7a772ead6ce7e1de82bfb04c485f3db8ec744f72925af5735e29a22cc18e" +dependencies = [ + "const-random-macro", + "proc-macro-hack", +] + +[[package]] +name = "const-random-macro" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d7d6ab3c3a2282db210df5f02c4dab6e0a7057af0fb7ebd4070f30fe05c0ddb" +dependencies = [ + "getrandom", + "once_cell", + "proc-macro-hack", + "tiny-keccak", +] + [[package]] name = "criterion" version = "0.5.1" @@ -555,6 +577,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "either" version = "1.9.0" @@ -1097,6 +1125,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + [[package]] name = "proc-macro2" version = "1.0.66" @@ -1449,6 +1483,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -2034,6 +2077,7 @@ dependencies = [ "async-compat", "bytes", "camino", + "const-random", "log", "once_cell", "oneshot", diff --git a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs index f36a5bda2f..edcfb6e810 100644 --- a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs +++ b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs @@ -292,7 +292,7 @@ impl KotlinCodeOracle { FfiType::Int64 | FfiType::UInt64 => "Long".to_string(), FfiType::Float32 => "Float".to_string(), FfiType::Float64 => "Double".to_string(), - FfiType::RustArcPtr(_) => "Pointer".to_string(), + FfiType::Handle => "UniffiHandle".to_string(), FfiType::RustBuffer(maybe_suffix) => { format!("RustBuffer{}", maybe_suffix.as_deref().unwrap_or_default()) } @@ -300,7 +300,6 @@ impl KotlinCodeOracle { FfiType::ForeignCallback => "ForeignCallback".to_string(), FfiType::ForeignExecutorHandle => "USize".to_string(), FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback".to_string(), - FfiType::RustFutureHandle => "Pointer".to_string(), FfiType::RustFutureContinuationCallback => { "UniFffiRustFutureContinuationCallbackType".to_string() } diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt b/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt index 4d1c099a02..5c5a2ed42a 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt @@ -13,10 +13,10 @@ internal object uniffiRustFutureContinuationCallback: UniFffiRustFutureContinuat } internal suspend fun uniffiRustCallAsync( - rustFuture: Pointer, - pollFunc: (Pointer, UniFffiRustFutureContinuationCallbackType, USize) -> Unit, - completeFunc: (Pointer, RustCallStatus) -> F, - freeFunc: (Pointer) -> Unit, + rustFuture: UniffiHandle, + pollFunc: (UniffiHandle, UniFffiRustFutureContinuationCallbackType, USize) -> Unit, + completeFunc: (UniffiHandle, RustCallStatus) -> F, + freeFunc: (UniffiHandle) -> Unit, liftFunc: (F) -> T, errorHandler: CallStatusErrorHandler ): T { diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt b/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt index 382a5f7413..6adbeea566 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt @@ -1,3 +1,5 @@ +typealias UniffiHandle = Long + // A handful of classes and functions to support the generated data structures. // This would be a good candidate for isolating in its own ffi-support lib. // Error runtime. diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt b/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt index 1e3ead5a7e..b9ede7b453 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt @@ -32,18 +32,18 @@ inline fun T.use(block: (T) -> R) = // The base class for all UniFFI Object types. // -// This class provides core operations for working with the Rust `Arc` pointer to -// the live Rust struct on the other side of the FFI. +// This class provides core operations for working with the Rust handle to the live Rust struct on +// the other side of the FFI. // // There's some subtlety here, because we have to be careful not to operate on a Rust // struct after it has been dropped, and because we must expose a public API for freeing // the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: // -// * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct. -// Method calls need to read this pointer from the object's state and pass it in to +// * Each `FFIObject` instance holds an opaque handle to the underlying Rust struct. +// Method calls need to read this handle from the object's state and pass it in to // the Rust FFI. // -// * When an `FFIObject` is no longer needed, its pointer should be passed to a +// * When an `FFIObject` is no longer needed, its handle should be passed to a // special destructor function provided by the Rust FFI, which will drop the // underlying Rust struct. // @@ -60,13 +60,13 @@ inline fun T.use(block: (T) -> R) = // the destructor has been called, and must never call the destructor more than once. // Doing so may trigger memory unsafety. // -// If we try to implement this with mutual exclusion on access to the pointer, there is the +// If we try to implement this with mutual exclusion on access to the handle, there is the // possibility of a race between a method call and a concurrent call to `destroy`: // -// * Thread A starts a method call, reads the value of the pointer, but is interrupted -// before it can pass the pointer over the FFI to Rust. +// * Thread A starts a method call, reads the value of the handle, but is interrupted +// before it can pass the handle over the FFI to Rust. // * Thread B calls `destroy` and frees the underlying Rust struct. -// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// * Thread A resumes, passing the already-read handle value to Rust and triggering // a use-after-free. // // One possible solution would be to use a `ReadWriteLock`, with each method call taking @@ -112,7 +112,7 @@ inline fun T.use(block: (T) -> R) = // [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 // abstract class FFIObject( - protected val pointer: Pointer + internal val handle: UniffiHandle ): Disposable, AutoCloseable { private val wasDestroyed = AtomicBoolean(false) @@ -138,7 +138,7 @@ abstract class FFIObject( this.destroy() } - internal inline fun callWithPointer(block: (ptr: Pointer) -> R): R { + internal inline fun callWithHandle(block: (handle: UniffiHandle) -> R): R { // Check and increment the call counter, to keep the object alive. // This needs a compare-and-set retry loop in case of concurrent updates. do { @@ -150,9 +150,9 @@ abstract class FFIObject( throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") } } while (! this.callCounter.compareAndSet(c, c + 1L)) - // Now we can safely do the method call without the pointer being freed concurrently. + // Now we can safely do the method call without the handle being freed concurrently. try { - return block(this.pointer) + return block(this.handle) } finally { // This decrement always matches the increment we performed above. if (this.callCounter.decrementAndGet() == 0L) { diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt index cef6be881f..ddb7b999aa 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt @@ -6,8 +6,8 @@ {% include "Interface.kt" %} class {{ impl_class_name }}( - pointer: Pointer -) : FFIObject(pointer), {{ interface_name }}{ + handle: UniffiHandle +) : FFIObject(handle), {{ interface_name }}{ {%- match obj.primary_constructor() %} {%- when Some with (cons) %} @@ -26,7 +26,7 @@ class {{ impl_class_name }}( */ override protected fun freeRustArcPtr() { rustCall() { status -> - _UniFFILib.INSTANCE.{{ obj.ffi_object_free().name() }}(this.pointer, status) + _UniFFILib.INSTANCE.{{ obj.ffi_object_free().name() }}(this.handle, status) } } @@ -40,9 +40,9 @@ class {{ impl_class_name }}( @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") override suspend fun {{ meth.name()|fn_name }}({%- call kt::arg_list_decl(meth) -%}){% match meth.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name }}{% when None %}{%- endmatch %} { return uniffiRustCallAsync( - callWithPointer { thisPtr -> + callWithHandle { uniffiHandle -> _UniFFILib.INSTANCE.{{ meth.ffi_func().name() }}( - thisPtr, + uniffiHandle, {% call kt::arg_list_lowered(meth) %} ) }, @@ -69,7 +69,7 @@ class {{ impl_class_name }}( {%- match meth.return_type() -%} {%- when Some with (return_type) -%} override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}): {{ return_type|type_name }} = - callWithPointer { + callWithHandle { {%- call kt::to_ffi_call_with_prefix("it", meth) %} }.let { {{ return_type|lift_fn }}(it) @@ -77,7 +77,7 @@ class {{ impl_class_name }}( {%- when None -%} override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}) = - callWithPointer { + callWithHandle { {%- call kt::to_ffi_call_with_prefix("it", meth) %} } {% endmatch %} @@ -103,35 +103,31 @@ class {{ impl_class_name }}( {% include "CallbackInterfaceImpl.kt" %} {%- endif %} -public object {{ obj|ffi_converter_name }}: FfiConverter<{{ type_name }}, Pointer> { +public object {{ obj|ffi_converter_name }}: FfiConverter<{{ type_name }}, UniffiHandle> { {%- if obj.is_trait_interface() %} internal val handleMap = ConcurrentHandleMap<{{ interface_name }}>() {%- endif %} - override fun lower(value: {{ type_name }}): Pointer { + override fun lower(value: {{ type_name }}): UniffiHandle { {%- match obj.imp() %} {%- when ObjectImpl::Struct %} - return value.callWithPointer { it } + return value.handle {%- when ObjectImpl::Trait %} - return Pointer(handleMap.insert(value)) + return UniffiHandle(handleMap.insert(value)) {%- endmatch %} } - override fun lift(value: Pointer): {{ type_name }} { + override fun lift(value: UniffiHandle): {{ type_name }} { return {{ impl_class_name }}(value) } override fun read(buf: ByteBuffer): {{ type_name }} { - // The Rust code always writes pointers as 8 bytes, and will - // fail to compile if they don't fit. - return lift(Pointer(buf.getLong())) + return lift(buf.getLong()) } override fun allocationSize(value: {{ type_name }}) = 8 override fun write(value: {{ type_name }}, buf: ByteBuffer) { - // The Rust code always expects pointers written as 8 bytes, - // and will fail to compile if they don't fit. - buf.putLong(Pointer.nativeValue(lower(value))) + buf.putLong(lower(value)) } } diff --git a/uniffi_bindgen/src/bindings/python/gen_python/mod.rs b/uniffi_bindgen/src/bindings/python/gen_python/mod.rs index 9199bbefd0..3ab657655d 100644 --- a/uniffi_bindgen/src/bindings/python/gen_python/mod.rs +++ b/uniffi_bindgen/src/bindings/python/gen_python/mod.rs @@ -311,7 +311,7 @@ impl PythonCodeOracle { FfiType::UInt64 => "ctypes.c_uint64".to_string(), FfiType::Float32 => "ctypes.c_float".to_string(), FfiType::Float64 => "ctypes.c_double".to_string(), - FfiType::RustArcPtr(_) => "ctypes.c_void_p".to_string(), + FfiType::Handle => "ctypes.c_int64".to_string(), FfiType::RustBuffer(maybe_suffix) => match maybe_suffix { Some(suffix) => format!("_UniffiRustBuffer{suffix}"), None => "_UniffiRustBuffer".to_string(), @@ -321,7 +321,6 @@ impl PythonCodeOracle { // Pointer to an `asyncio.EventLoop` instance FfiType::ForeignExecutorHandle => "ctypes.c_size_t".to_string(), FfiType::ForeignExecutorCallback => "_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T".to_string(), - FfiType::RustFutureHandle => "ctypes.c_void_p".to_string(), FfiType::RustFutureContinuationCallback => "_UNIFFI_FUTURE_CONTINUATION_T".to_string(), FfiType::RustFutureContinuationData => "ctypes.c_size_t".to_string(), } diff --git a/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py b/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py index 097fcd4d1d..8cce1500db 100644 --- a/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py @@ -5,29 +5,29 @@ {% include "Protocol.py" %} class {{ impl_name }}: - _pointer: ctypes.c_void_p + _uniffi_handle: ctypes.c_int64 {%- match obj.primary_constructor() %} {%- when Some with (cons) %} def __init__(self, {% call py::arg_list_decl(cons) -%}): {%- call py::setup_args_extra_indent(cons) %} - self._pointer = {% call py::to_ffi_call(cons) %} + self._uniffi_handle = {% call py::to_ffi_call(cons) %} {%- when None %} {%- endmatch %} def __del__(self): # In case of partial initialization of instances. - pointer = getattr(self, "_pointer", None) - if pointer is not None: - _rust_call(_UniffiLib.{{ obj.ffi_object_free().name() }}, pointer) + handle = getattr(self, "_uniffi_handle", None) + if handle is not None: + _rust_call(_UniffiLib.{{ obj.ffi_object_free().name() }}, handle) # Used by alternative constructors or any methods which return this type. @classmethod - def _make_instance_(cls, pointer): + def _make_instance_(cls, handle): # Lightly yucky way to bypass the usual __init__ logic - # and just create a new instance with the required pointer. + # and just create a new instance with the required handle. inst = cls.__new__(cls) - inst._pointer = pointer + inst._uniffi_handle = handle return inst {%- for cons in obj.alternate_constructors() %} @@ -36,8 +36,8 @@ def _make_instance_(cls, pointer): def {{ cons.name()|fn_name }}(cls, {% call py::arg_list_decl(cons) %}): {%- call py::setup_args_extra_indent(cons) %} # Call the (fallible) function before creating any half-baked object instances. - pointer = {% call py::to_ffi_call(cons) %} - return cls._make_instance_(pointer) + uniffi_handle = {% call py::to_ffi_call(cons) %} + return cls._make_instance_(uniffi_handle) {% endfor %} {%- for meth in obj.methods() -%} @@ -55,13 +55,13 @@ def __eq__(self, other: object) -> {{ eq.return_type().unwrap()|type_name }}: if not isinstance(other, {{ type_name }}): return NotImplemented - return {{ eq.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._pointer", eq) %}) + return {{ eq.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._uniffi_handle", eq) %}) def __ne__(self, other: object) -> {{ ne.return_type().unwrap()|type_name }}: if not isinstance(other, {{ type_name }}): return NotImplemented - return {{ ne.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._pointer", ne) %}) + return {{ ne.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._uniffi_handle", ne) %}) {%- when UniffiTrait::Hash { hash } %} {%- call py::method_decl("__hash__", hash) %} {% endmatch %} @@ -89,7 +89,7 @@ def lower(value: {{ protocol_name }}): {%- when ObjectImpl::Struct %} if not isinstance(value, {{ impl_name }}): raise TypeError("Expected {{ impl_name }} instance, {} found".format(type(value).__name__)) - return value._pointer + return value._uniffi_handle {%- when ObjectImpl::Trait %} return {{ ffi_converter_name }}._handle_map.insert(value) {%- endmatch %} @@ -97,8 +97,6 @@ def lower(value: {{ protocol_name }}): @classmethod def read(cls, buf: _UniffiRustBuffer): ptr = buf.read_u64() - if ptr == 0: - raise InternalError("Raw pointer value was null") return cls.lift(ptr) @classmethod diff --git a/uniffi_bindgen/src/bindings/python/templates/macros.py b/uniffi_bindgen/src/bindings/python/templates/macros.py index ef3b1bb94d..e6cafa9f9f 100644 --- a/uniffi_bindgen/src/bindings/python/templates/macros.py +++ b/uniffi_bindgen/src/bindings/python/templates/macros.py @@ -104,7 +104,7 @@ def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}): {%- call setup_args_extra_indent(meth) %} return _uniffi_rust_call_async( _UniffiLib.{{ meth.ffi_func().name() }}( - self._pointer, {% call arg_list_lowered(meth) %} + self._uniffi_handle, {% call arg_list_lowered(meth) %} ), _UniffiLib.{{ meth.ffi_rust_future_poll(ci) }}, _UniffiLib.{{ meth.ffi_rust_future_complete(ci) }}, @@ -133,14 +133,14 @@ def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}): def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}) -> "{{ return_type|type_name }}": {%- call setup_args_extra_indent(meth) %} return {{ return_type|lift_fn }}( - {% call to_ffi_call_with_prefix("self._pointer", meth) %} + {% call to_ffi_call_with_prefix("self._uniffi_handle", meth) %} ) {%- when None %} def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}): {%- call setup_args_extra_indent(meth) %} - {% call to_ffi_call_with_prefix("self._pointer", meth) %} + {% call to_ffi_call_with_prefix("self._uniffi_handle", meth) %} {% endmatch %} {% endif %} diff --git a/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs b/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs index e6defe65cc..914ec397c2 100644 --- a/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs +++ b/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs @@ -150,7 +150,7 @@ mod filters { FfiType::UInt64 => ":uint64".to_string(), FfiType::Float32 => ":float".to_string(), FfiType::Float64 => ":double".to_string(), - FfiType::RustArcPtr(_) => ":pointer".to_string(), + FfiType::Handle => ":int64".to_string(), FfiType::RustBuffer(_) => "RustBuffer.by_value".to_string(), FfiType::ForeignBytes => "ForeignBytes".to_string(), // Callback interfaces are not yet implemented, but this needs to return something in @@ -162,9 +162,7 @@ mod filters { FfiType::ForeignExecutorHandle => { unimplemented!("Foreign executors are not implemented") } - FfiType::RustFutureHandle - | FfiType::RustFutureContinuationCallback - | FfiType::RustFutureContinuationData => { + FfiType::RustFutureContinuationCallback | FfiType::RustFutureContinuationData => { unimplemented!("Async functions are not implemented") } }) diff --git a/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb b/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb index 677c5c729b..21ce0167fc 100644 --- a/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb +++ b/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb @@ -1,44 +1,43 @@ class {{ obj.name()|class_name_rb }} - # A private helper for initializing instances of the class from a raw pointer, + # A private helper for initializing instances of the class from a handle, # bypassing any initialization logic and ensuring they are GC'd properly. - def self._uniffi_allocate(pointer) - pointer.autorelease = false + def self._uniffi_allocate(handle) inst = allocate - inst.instance_variable_set :@pointer, pointer - ObjectSpace.define_finalizer(inst, _uniffi_define_finalizer_by_pointer(pointer, inst.object_id)) + inst.instance_variable_set :@handle, handle + ObjectSpace.define_finalizer(inst, _uniffi_define_finalizer_by_handle(handle, inst.object_id)) return inst end # A private helper for registering an object finalizer. # N.B. it's important that this does not capture a reference - # to the actual instance, only its underlying pointer. - def self._uniffi_define_finalizer_by_pointer(pointer, object_id) + # to the actual instance, only its underlying handle. + def self._uniffi_define_finalizer_by_handle(handle, object_id) Proc.new do |_id| {{ ci.namespace()|class_name_rb }}.rust_call( :{{ obj.ffi_object_free().name() }}, - pointer + handle ) end end - # A private helper for lowering instances into a raw pointer. + # A private helper for lowering instances into a handle. # This does an explicit typecheck, because accidentally lowering a different type of # object in a place where this type is expected, could lead to memory unsafety. def self._uniffi_lower(inst) if not inst.is_a? self raise TypeError.new "Expected a {{ obj.name()|class_name_rb }} instance, got #{inst}" end - return inst.instance_variable_get :@pointer + return inst.instance_variable_get :@handle end {%- match obj.primary_constructor() %} {%- when Some with (cons) %} def initialize({% call rb::arg_list_decl(cons) -%}) {%- call rb::coerce_args_extra_indent(cons) %} - pointer = {% call rb::to_ffi_call(cons) %} - @pointer = pointer - ObjectSpace.define_finalizer(self, self.class._uniffi_define_finalizer_by_pointer(pointer, self.object_id)) + handle = {% call rb::to_ffi_call(cons) %} + @handle = handle + ObjectSpace.define_finalizer(self, self.class._uniffi_define_finalizer_by_handle(handle, self.object_id)) end {%- when None %} {%- endmatch %} @@ -48,7 +47,7 @@ def self.{{ cons.name()|fn_name_rb }}({% call rb::arg_list_decl(cons) %}) {%- call rb::coerce_args_extra_indent(cons) %} # Call the (fallible) function before creating any half-baked object instances. # Lightly yucky way to bypass the usual "initialize" logic - # and just create a new instance with the required pointer. + # and just create a new instance with the required handle. return _uniffi_allocate({% call rb::to_ffi_call(cons) %}) end {% endfor %} @@ -59,14 +58,14 @@ def self.{{ cons.name()|fn_name_rb }}({% call rb::arg_list_decl(cons) %}) {%- when Some with (return_type) -%} def {{ meth.name()|fn_name_rb }}({% call rb::arg_list_decl(meth) %}) {%- call rb::coerce_args_extra_indent(meth) %} - result = {% call rb::to_ffi_call_with_prefix("@pointer", meth) %} + result = {% call rb::to_ffi_call_with_prefix("@handle", meth) %} return {{ "result"|lift_rb(return_type) }} end {%- when None -%} def {{ meth.name()|fn_name_rb }}({% call rb::arg_list_decl(meth) %}) {%- call rb::coerce_args_extra_indent(meth) %} - {% call rb::to_ffi_call_with_prefix("@pointer", meth) %} + {% call rb::to_ffi_call_with_prefix("@handle", meth) %} end {% endmatch %} {% endfor %} diff --git a/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb b/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb index 8749139116..24f5d83ff6 100644 --- a/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb +++ b/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb @@ -163,8 +163,8 @@ def write_{{ canonical_type_name }}(v) # The Object type {{ object_name }}. def write_{{ canonical_type_name }}(obj) - pointer = {{ object_name|class_name_rb}}._uniffi_lower obj - pack_into(8, 'Q>', pointer.address) + handle = {{ object_name|class_name_rb}}._uniffi_lower obj + pack_into(8, 'Q>', handle) end {% when Type::Enum { name: enum_name, module_path } -%} diff --git a/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb b/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb index b085dddf15..ed593672d7 100644 --- a/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb +++ b/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb @@ -154,8 +154,8 @@ def read{{ canonical_type_name }} # The Object type {{ object_name }}. def read{{ canonical_type_name }} - pointer = FFI::Pointer.new unpack_from 8, 'Q>' - return {{ object_name|class_name_rb }}._uniffi_allocate(pointer) + handle = unpack_from 8, 'Q>' + return {{ object_name|class_name_rb }}._uniffi_allocate(handle) end {% when Type::Enum { name, module_path } -%} diff --git a/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs b/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs index 1ba385d361..529fa2c9ca 100644 --- a/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs +++ b/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs @@ -455,16 +455,19 @@ impl SwiftCodeOracle { FfiType::UInt64 => "UInt64".into(), FfiType::Float32 => "Float".into(), FfiType::Float64 => "Double".into(), - FfiType::RustArcPtr(_) => "UnsafeMutableRawPointer".into(), + // Other bindings use an type alias for `Handle`, but this isn't so easy with Swift: + // * When multiple crates are built together, all swift files get merged into a + // single module which can lead to namespace conflicts. + // * `fileprivate` type aliases can't be used in a public API, even if the actual + // type is public. + FfiType::Handle => "Int64".into(), FfiType::RustBuffer(_) => "RustBuffer".into(), FfiType::ForeignBytes => "ForeignBytes".into(), FfiType::ForeignCallback => "ForeignCallback".into(), FfiType::ForeignExecutorHandle => "Int".into(), FfiType::ForeignExecutorCallback => "ForeignExecutorCallback".into(), FfiType::RustFutureContinuationCallback => "UniFfiRustFutureContinuation".into(), - FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => { - "UnsafeMutableRawPointer".into() - } + FfiType::RustFutureContinuationData => "UnsafeMutableRawPointer".into(), } } @@ -472,7 +475,6 @@ impl SwiftCodeOracle { match ffi_type { FfiType::ForeignCallback | FfiType::ForeignExecutorCallback - | FfiType::RustFutureHandle | FfiType::RustFutureContinuationCallback | FfiType::RustFutureContinuationData => { format!("{} _Nonnull", self.ffi_type_label_raw(ffi_type)) @@ -571,7 +573,7 @@ pub mod filters { FfiType::UInt64 => "uint64_t".into(), FfiType::Float32 => "float".into(), FfiType::Float64 => "double".into(), - FfiType::RustArcPtr(_) => "void*_Nonnull".into(), + FfiType::Handle => "int64_t".into(), FfiType::RustBuffer(_) => "RustBuffer".into(), FfiType::ForeignBytes => "ForeignBytes".into(), FfiType::ForeignCallback => "ForeignCallback _Nonnull".into(), @@ -580,9 +582,7 @@ pub mod filters { FfiType::RustFutureContinuationCallback => { "UniFfiRustFutureContinuation _Nonnull".into() } - FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => { - "void* _Nonnull".into() - } + FfiType::RustFutureContinuationData => "void* _Nonnull".into(), }) } diff --git a/uniffi_bindgen/src/bindings/swift/templates/Async.swift b/uniffi_bindgen/src/bindings/swift/templates/Async.swift index f947408182..d02c884766 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/Async.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/Async.swift @@ -2,10 +2,10 @@ private let UNIFFI_RUST_FUTURE_POLL_READY: Int8 = 0 private let UNIFFI_RUST_FUTURE_POLL_MAYBE_READY: Int8 = 1 fileprivate func uniffiRustCallAsync( - rustFutureFunc: () -> UnsafeMutableRawPointer, - pollFunc: (UnsafeMutableRawPointer, @escaping UniFfiRustFutureContinuation, UnsafeMutableRawPointer) -> (), - completeFunc: (UnsafeMutableRawPointer, UnsafeMutablePointer) -> F, - freeFunc: (UnsafeMutableRawPointer) -> (), + rustFutureFunc: () -> Int64, + pollFunc: (Int64, @escaping UniFfiRustFutureContinuation, UnsafeMutableRawPointer) -> (), + completeFunc: (Int64, UnsafeMutablePointer) -> F, + freeFunc: (Int64) -> (), liftFunc: (F) throws -> T, errorHandler: ((RustBuffer) throws -> Error)? ) async throws -> T { diff --git a/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift index b63631f40e..0ab73c2550 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift @@ -5,31 +5,31 @@ {% include "Protocol.swift" %} public class {{ impl_class_name }}: {{ protocol_name }} { - fileprivate let pointer: UnsafeMutableRawPointer + fileprivate let handle: Int64 // TODO: We'd like this to be `private` but for Swifty reasons, // we can't implement `FfiConverter` without making this `required` and we can't // make it `required` without making it `public`. - required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { - self.pointer = pointer + required init(handle: Int64) { + self.handle = handle } {%- match obj.primary_constructor() %} {%- when Some with (cons) %} public convenience init({% call swift::arg_list_decl(cons) -%}) {% call swift::throws(cons) %} { - self.init(unsafeFromRawPointer: {% call swift::to_ffi_call(cons) %}) + self.init(handle: {% call swift::to_ffi_call(cons) %}) } {%- when None %} {%- endmatch %} deinit { - try! rustCall { {{ obj.ffi_object_free().name() }}(pointer, $0) } + try! rustCall { {{ obj.ffi_object_free().name() }}(handle, $0) } } {% for cons in obj.alternate_constructors() %} public static func {{ cons.name()|fn_name }}({% call swift::arg_list_decl(cons) %}) {% call swift::throws(cons) %} -> {{ impl_class_name }} { - return {{ impl_class_name }}(unsafeFromRawPointer: {% call swift::to_ffi_call(cons) %}) + return {{ impl_class_name }}(handle: {% call swift::to_ffi_call(cons) %}) } {% endfor %} @@ -42,7 +42,7 @@ public class {{ impl_class_name }}: {{ protocol_name }} { return {% call swift::try(meth) %} await uniffiRustCallAsync( rustFutureFunc: { {{ meth.ffi_func().name() }}( - self.pointer + self.handle {%- for arg in meth.arguments() -%} , {{ arg|lower_fn }}({{ arg.name()|var_name }}) @@ -75,14 +75,14 @@ public class {{ impl_class_name }}: {{ protocol_name }} { public func {{ meth.name()|fn_name }}({% call swift::arg_list_decl(meth) %}) {% call swift::throws(meth) %} -> {{ return_type|type_name }} { return {% call swift::try(meth) %} {{ return_type|lift_fn }}( - {% call swift::to_ffi_call_with_prefix("self.pointer", meth) %} + {% call swift::to_ffi_call_with_prefix("self.handle", meth) %} ) } {%- when None %} public func {{ meth.name()|fn_name }}({% call swift::arg_list_decl(meth) %}) {% call swift::throws(meth) %} { - {% call swift::to_ffi_call_with_prefix("self.pointer", meth) %} + {% call swift::to_ffi_call_with_prefix("self.handle", meth) %} } {%- endmatch -%} @@ -102,17 +102,17 @@ public struct {{ ffi_converter_name }}: FfiConverter { fileprivate static var handleMap = UniFFICallbackHandleMap<{{ type_name }}>() {%- endif %} - typealias FfiType = UnsafeMutableRawPointer + typealias FfiType = Int64 typealias SwiftType = {{ type_name }} - public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> {{ type_name }} { - return {{ impl_class_name }}(unsafeFromRawPointer: pointer) + public static func lift(_ handle: Int64) throws -> {{ type_name }} { + return {{ impl_class_name }}(handle: handle) } - public static func lower(_ value: {{ type_name }}) -> UnsafeMutableRawPointer { + public static func lower(_ value: {{ type_name }}) -> Int64 { {%- match obj.imp() %} {%- when ObjectImpl::Struct %} - return value.pointer + return value.handle {%- when ObjectImpl::Trait %} guard let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: handleMap.insert(obj: value))) else { fatalError("Cast to UnsafeMutableRawPointer failed") @@ -122,20 +122,11 @@ public struct {{ ffi_converter_name }}: FfiConverter { } public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} { - let v: UInt64 = try readInt(&buf) - // The Rust code won't compile if a pointer won't fit in a UInt64. - // We have to go via `UInt` because that's the thing that's the size of a pointer. - let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) - if (ptr == nil) { - throw UniffiInternalError.unexpectedNullPointer - } - return try lift(ptr!) + return try lift(try readInt(&buf)) } public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) { - // This fiddling is because `Int` is the thing that's the same size as a pointer. - // The Rust code won't compile if a pointer won't fit in a `UInt64`. - writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + writeInt(&buf, lower(value)) } } @@ -143,10 +134,10 @@ public struct {{ ffi_converter_name }}: FfiConverter { We always write these public functions just in case the enum is used as an external type by another crate. #} -public func {{ ffi_converter_name }}_lift(_ pointer: UnsafeMutableRawPointer) throws -> {{ type_name }} { - return try {{ ffi_converter_name }}.lift(pointer) +public func {{ ffi_converter_name }}_lift(_ handle: Int64) throws -> {{ type_name }} { + return try {{ ffi_converter_name }}.lift(handle) } -public func {{ ffi_converter_name }}_lower(_ value: {{ type_name }}) -> UnsafeMutableRawPointer { +public func {{ ffi_converter_name }}_lower(_ value: {{ type_name }}) -> Int64 { return {{ ffi_converter_name }}.lower(value) } diff --git a/uniffi_bindgen/src/interface/ffi.rs b/uniffi_bindgen/src/interface/ffi.rs index 8f23ccbb90..bd0fafae80 100644 --- a/uniffi_bindgen/src/interface/ffi.rs +++ b/uniffi_bindgen/src/interface/ffi.rs @@ -33,11 +33,8 @@ pub enum FfiType { Int64, Float32, Float64, - /// A `*const c_void` pointer to a rust-owned `Arc`. - /// If you've got one of these, you must call the appropriate rust function to free it. - /// The templates will generate a unique `free` function for each T. - /// The inner string references the name of the `T` type. - RustArcPtr(String), + /// 64-bit handle. See `slab.rs` for details. + Handle, /// A byte buffer allocated by rust, and owned by whoever currently holds it. /// If you've got one of these, you must either call the appropriate rust function to free it /// or pass it to someone that will. @@ -54,8 +51,6 @@ pub enum FfiType { ForeignExecutorHandle, /// Pointer to the callback function that's invoked to schedule calls with a ForeignExecutor ForeignExecutorCallback, - /// Pointer to a Rust future - RustFutureHandle, /// Continuation function for a Rust future RustFutureContinuationCallback, RustFutureContinuationData, @@ -90,8 +85,7 @@ impl From<&Type> for FfiType { // Byte strings are also always owned rust values. // We might add a separate type for borrowed byte strings in future as well. Type::Bytes => FfiType::RustBuffer(None), - // Objects are pointers to an Arc<> - Type::Object { name, .. } => FfiType::RustArcPtr(name.to_owned()), + Type::Object { .. } => FfiType::Handle, // Callback interfaces are passed as opaque integer handles. Type::CallbackInterface { .. } => FfiType::UInt64, Type::ForeignExecutor => FfiType::ForeignExecutorHandle, @@ -104,10 +98,9 @@ impl From<&Type> for FfiType { | Type::Timestamp | Type::Duration => FfiType::RustBuffer(None), Type::External { - name, kind: ExternalKind::Interface, .. - } => FfiType::RustArcPtr(name.clone()), + } => FfiType::Handle, Type::External { name, kind: ExternalKind::DataClass, @@ -194,7 +187,7 @@ impl FfiFunction { ) { self.arguments = args.into_iter().collect(); if self.is_async() { - self.return_type = Some(FfiType::RustFutureHandle); + self.return_type = Some(FfiType::Handle); self.has_rust_call_status_arg = false; } else { self.return_type = return_type; diff --git a/uniffi_bindgen/src/interface/mod.rs b/uniffi_bindgen/src/interface/mod.rs index c56a3cd65c..472dc68790 100644 --- a/uniffi_bindgen/src/interface/mod.rs +++ b/uniffi_bindgen/src/interface/mod.rs @@ -443,7 +443,7 @@ impl ComponentInterface { arguments: vec![ FfiArgument { name: "handle".to_owned(), - type_: FfiType::RustFutureHandle, + type_: FfiType::Handle, }, FfiArgument { name: "callback".to_owned(), @@ -469,7 +469,7 @@ impl ComponentInterface { is_async: false, arguments: vec![FfiArgument { name: "handle".to_owned(), - type_: FfiType::RustFutureHandle, + type_: FfiType::Handle, }], return_type: return_ffi_type, has_rust_call_status_arg: true, @@ -484,7 +484,7 @@ impl ComponentInterface { is_async: false, arguments: vec![FfiArgument { name: "handle".to_owned(), - type_: FfiType::RustFutureHandle, + type_: FfiType::Handle, }], return_type: None, has_rust_call_status_arg: false, @@ -499,7 +499,7 @@ impl ComponentInterface { is_async: false, arguments: vec![FfiArgument { name: "handle".to_owned(), - type_: FfiType::RustFutureHandle, + type_: FfiType::Handle, }], return_type: None, has_rust_call_status_arg: false, @@ -521,7 +521,7 @@ impl ComponentInterface { FfiType::Int64 => format!("ffi_{namespace}_{base_name}_i64"), FfiType::Float32 => format!("ffi_{namespace}_{base_name}_f32"), FfiType::Float64 => format!("ffi_{namespace}_{base_name}_f64"), - FfiType::RustArcPtr(_) => format!("ffi_{namespace}_{base_name}_pointer"), + FfiType::Handle => format!("ffi_{namespace}_{base_name}_handle"), FfiType::RustBuffer(_) => format!("ffi_{namespace}_{base_name}_rust_buffer"), _ => unimplemented!("FFI return type: {t:?}"), }, @@ -622,9 +622,9 @@ impl ComponentInterface { Some(FfiType::Int64), Some(FfiType::Float32), Some(FfiType::Float64), - // RustBuffer and RustArcPtr have an inner field which doesn't affect the rust future + Some(FfiType::Handle), + // RustBuffer has an inner field which doesn't affect the rust future // complete scaffolding function, so we just use a placeholder value here. - Some(FfiType::RustArcPtr("".to_owned())), Some(FfiType::RustBuffer(None)), None, ]; diff --git a/uniffi_bindgen/src/interface/object.rs b/uniffi_bindgen/src/interface/object.rs index d79e7fccb1..f38ea8cfbc 100644 --- a/uniffi_bindgen/src/interface/object.rs +++ b/uniffi_bindgen/src/interface/object.rs @@ -190,7 +190,7 @@ impl Object { assert!(!self.ffi_func_free.name().is_empty()); self.ffi_func_free.arguments = vec![FfiArgument { name: "ptr".to_string(), - type_: FfiType::RustArcPtr(self.name.to_string()), + type_: FfiType::Handle, }]; self.ffi_func_free.return_type = None; self.ffi_func_free.is_object_free_function = true; @@ -342,7 +342,7 @@ impl Constructor { fn derive_ffi_func(&mut self) { assert!(!self.ffi_func.name().is_empty()); self.ffi_func.arguments = self.arguments.iter().map(Into::into).collect(); - self.ffi_func.return_type = Some(FfiType::RustArcPtr(self.object_name.clone())); + self.ffi_func.return_type = Some(FfiType::Handle); } pub fn iter_types(&self) -> TypeIterator<'_> { diff --git a/uniffi_core/Cargo.toml b/uniffi_core/Cargo.toml index 9b81c3178f..04756c03ce 100644 --- a/uniffi_core/Cargo.toml +++ b/uniffi_core/Cargo.toml @@ -16,6 +16,7 @@ anyhow = "1" async-compat = { version = "0.2.1", optional = true } bytes = "1.3" camino = "1.0.8" +const-random = "0.1.15" log = "0.4" once_cell = "1.10.0" # Enable "async" so that receivers implement Future, no need for "std" since we don't block on them. diff --git a/uniffi_core/src/ffi/ffidefault.rs b/uniffi_core/src/ffi/ffidefault.rs index 1f86f6b13b..9d8687fbc5 100644 --- a/uniffi_core/src/ffi/ffidefault.rs +++ b/uniffi_core/src/ffi/ffidefault.rs @@ -39,6 +39,12 @@ impl FfiDefault for () { fn ffi_default() {} } +impl FfiDefault for crate::Handle { + fn ffi_default() -> Self { + Self::from_raw(0) + } +} + impl FfiDefault for *const std::ffi::c_void { fn ffi_default() -> Self { std::ptr::null() diff --git a/uniffi_core/src/ffi/rustfuture.rs b/uniffi_core/src/ffi/rustfuture.rs index 0c1a24174b..e02926c78c 100644 --- a/uniffi_core/src/ffi/rustfuture.rs +++ b/uniffi_core/src/ffi/rustfuture.rs @@ -12,7 +12,7 @@ //! //! 0. At startup, register a [RustFutureContinuationCallback] by calling //! rust_future_continuation_callback_set. -//! 1. Call the scaffolding function to get a [RustFutureHandle] +//! 1. Call the scaffolding function to get a [Handle] //! 2a. In a loop: //! - Call [rust_future_poll] //! - Suspend the function until the [rust_future_poll] continuation function is called @@ -86,7 +86,10 @@ use std::{ task::{Context, Poll, Wake}, }; -use crate::{rust_call_with_out_status, FfiDefault, LowerReturn, RustCallStatus}; +use crate::{ + derive_ffi_traits, rust_call_with_out_status, FfiDefault, Handle, LowerReturn, RustCallStatus, + SlabAlloc, +}; /// Result code for [rust_future_poll]. This is passed to the continuation function. #[repr(i8)] @@ -104,37 +107,26 @@ pub enum RustFuturePoll { /// to continue progress on the future. pub type RustFutureContinuationCallback = extern "C" fn(callback_data: *const (), RustFuturePoll); -/// Opaque handle for a Rust future that's stored by the foreign language code -#[repr(transparent)] -pub struct RustFutureHandle(*const ()); - // === Public FFI API === -/// Create a new [RustFutureHandle] +/// Create a new [Handle] for a RustFuture /// /// For each exported async function, UniFFI will create a scaffolding function that uses this to -/// create the [RustFutureHandle] to pass to the foreign code. -pub fn rust_future_new(future: F, tag: UT) -> RustFutureHandle +/// create the [Handle] to pass to the foreign code. +pub fn rust_future_new(future: F, tag: UT) -> Handle where - // F is the future type returned by the exported async function. It needs to be Send + `static - // since it will move between threads for an indeterminate amount of time as the foreign - // executor calls polls it and the Rust executor wakes it. It does not need to by `Sync`, - // since we synchronize all access to the values. + // See the [RustFuture] struct for an explanation of these trait bounds F: Future + Send + 'static, - // T is the output of the Future. It needs to implement [LowerReturn]. Also it must be Send + - // 'static for the same reason as F. T: LowerReturn + Send + 'static, - // The UniFfiTag ZST. The Send + 'static bound is to keep rustc happy. UT: Send + 'static, + // Needed to create a Handle + dyn RustFutureFfi: SlabAlloc, { // Create a RustFuture and coerce to `Arc`, which is what we use to // implement the FFI - let future_ffi = RustFuture::new(future, tag) as Arc>; - // Box the Arc, to convert the wide pointer into a normal sized pointer so that we can pass it - // to the foreign code. - let boxed_ffi = Box::new(future_ffi); - // We can now create a RustFutureHandle - RustFutureHandle(Box::into_raw(boxed_ffi) as *mut ()) + as SlabAlloc>::insert( + RustFuture::new(future, tag) as Arc> + ) } /// Poll a Rust future @@ -145,14 +137,12 @@ where /// /// # Safety /// -/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] -pub unsafe fn rust_future_poll( - handle: RustFutureHandle, - callback: RustFutureContinuationCallback, - data: *const (), -) { - let future = &*(handle.0 as *mut Arc>); - future.clone().ffi_poll(callback, data) +/// The [Handle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_poll(handle: Handle, callback: RustFutureContinuationCallback, data: *const ()) +where + dyn RustFutureFfi: SlabAlloc, +{ + as SlabAlloc>::get_clone(handle).ffi_poll(callback, data) } /// Cancel a Rust future @@ -164,10 +154,12 @@ pub unsafe fn rust_future_poll( /// /// # Safety /// -/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] -pub unsafe fn rust_future_cancel(handle: RustFutureHandle) { - let future = &*(handle.0 as *mut Arc>); - future.clone().ffi_cancel() +/// The [Handle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_cancel(handle: Handle) +where + dyn RustFutureFfi: SlabAlloc, +{ + as SlabAlloc>::get_clone(handle).ffi_cancel() } /// Complete a Rust future @@ -177,15 +169,17 @@ pub unsafe fn rust_future_cancel(handle: RustFutureHandle) { /// /// # Safety /// -/// - The [RustFutureHandle] must not previously have been passed to [rust_future_free] +/// - The [Handle] must not previously have been passed to [rust_future_free] /// - The `T` param must correctly correspond to the [rust_future_new] call. It must /// be `>::ReturnType` -pub unsafe fn rust_future_complete( - handle: RustFutureHandle, +pub unsafe fn rust_future_complete( + handle: Handle, out_status: &mut RustCallStatus, -) -> ReturnType { - let future = &*(handle.0 as *mut Arc>); - future.ffi_complete(out_status) +) -> ReturnType +where + dyn RustFutureFfi: SlabAlloc, +{ + as SlabAlloc>::get_clone(handle).ffi_complete(out_status) } /// Free a Rust future, dropping the strong reference and releasing all references held by the @@ -193,12 +187,30 @@ pub unsafe fn rust_future_complete( /// /// # Safety /// -/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] -pub unsafe fn rust_future_free(handle: RustFutureHandle) { - let future = Box::from_raw(handle.0 as *mut Arc>); - future.ffi_free() +/// The [Handle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_free(handle: Handle) +where + dyn RustFutureFfi: SlabAlloc, +{ + as SlabAlloc>::remove(handle).ffi_free() } +// Derive SlabAlloc for dyn RustFutureFfi for all FFI return types +derive_ffi_traits!(impl SlabAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl SlabAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl SlabAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl SlabAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl SlabAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl SlabAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl SlabAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl SlabAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl SlabAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl SlabAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl SlabAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl SlabAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl SlabAlloc for dyn RustFutureFfi<()>); +derive_ffi_traits!(impl SlabAlloc for dyn RustFutureFfi<*const ::std::ffi::c_void>); + /// Thread-safe storage for [RustFutureContinuationCallback] data /// /// The basic guarantee is that all data pointers passed in are passed out exactly once to the @@ -282,7 +294,7 @@ unsafe impl Sync for ContinuationDataCell {} /// Wraps the actual future we're polling struct WrappedFuture where - // See rust_future_new for an explanation of these trait bounds + // See the [RustFuture] struct for an explanation of these trait bounds F: Future + Send + 'static, T: LowerReturn + Send + 'static, UT: Send + 'static, @@ -296,7 +308,7 @@ where impl WrappedFuture where - // See rust_future_new for an explanation of these trait bounds + // See the [RustFuture] struct for an explanation of these trait bounds F: Future + Send + 'static, T: LowerReturn + Send + 'static, UT: Send + 'static, @@ -375,7 +387,7 @@ where // that we will treat the raw pointer properly, for example by not returning it twice. unsafe impl Send for WrappedFuture where - // See rust_future_new for an explanation of these trait bounds + // See the [RustFuture] struct for an explanation of these trait bounds F: Future + Send + 'static, T: LowerReturn + Send + 'static, UT: Send + 'static, @@ -385,7 +397,16 @@ where /// Future that the foreign code is awaiting struct RustFuture where - // See rust_future_new for an explanation of these trait bounds + // F is the future type returned by the exported async function. It needs to be Send + `static + // since it will move between threads for an indeterminate amount of time as the foreign + // executor calls polls it and the Rust executor wakes it. It does not need to by `Sync`, + // since we synchronize all access to the values. + F: Future + Send + 'static, + // T is the output of the Future. It needs to implement [LowerReturn]. Also it must be Send + + // 'static for the same reason as F. + T: LowerReturn + Send + 'static, + // The UniFfiTag ZST. The Send + 'static bound is to keep rustc happy. + UT: Send + 'static, F: Future + Send + 'static, T: LowerReturn + Send + 'static, UT: Send + 'static, @@ -401,7 +422,7 @@ where impl RustFuture where - // See rust_future_new for an explanation of these trait bounds + // See the [RustFuture] struct for an explanation of these trait bounds F: Future + Send + 'static, T: LowerReturn + Send + 'static, UT: Send + 'static, @@ -453,7 +474,7 @@ where impl Wake for RustFuture where - // See rust_future_new for an explanation of these trait bounds + // See the [RustFuture] struct for an explanation of these trait bounds F: Future + Send + 'static, T: LowerReturn + Send + 'static, UT: Send + 'static, @@ -479,7 +500,7 @@ where /// x86-64 machine . By parametrizing on `T::ReturnType` we can instead monomorphize by hand and /// only create those functions for each of the 13 possible FFI return types. #[doc(hidden)] -trait RustFutureFfi { +pub trait RustFutureFfi { fn ffi_poll(self: Arc, callback: RustFutureContinuationCallback, data: *const ()); fn ffi_cancel(&self); fn ffi_complete(&self, call_status: &mut RustCallStatus) -> ReturnType; @@ -488,7 +509,7 @@ trait RustFutureFfi { impl RustFutureFfi for RustFuture where - // See rust_future_new for an explanation of these trait bounds + // See the [RustFuture] struct for an explanation of these trait bounds F: Future + Send + 'static, T: LowerReturn + Send + 'static, UT: Send + 'static, diff --git a/uniffi_core/src/ffi/slab.rs b/uniffi_core/src/ffi/slab.rs index b813d328c5..721984583e 100644 --- a/uniffi_core/src/ffi/slab.rs +++ b/uniffi_core/src/ffi/slab.rs @@ -672,6 +672,22 @@ mod entry_tests { } } +/// Create a static Slab value with a random id +#[macro_export] +macro_rules! static_slab { + ($ident:ident, $($tt:tt)*) => { + #[cfg(not(loom))] + static $ident: $crate::Slab::<$($tt)*> = $crate::Slab::<$($tt)*>::new_with_id_and_foreign($crate::deps::const_random::const_random!(u8), false); + + // If loom is configured, then new_with_id_and_foreign won't be const so we need to wrap + // everything in a once_cell `Lazy` instance. + #[cfg(loom)] + static $ident: ::once_cell::sync::Lazy<$crate::Slab::<$($tt)*>> = ::once_cell::sync::Lazy::new(|| { + $crate::Slab::<$($tt)*>::new_with_id_and_foreign($crate::deps::const_random::const_random!(u8), false) + }); + }; +} + #[cfg(test)] mod slab_tests { use super::*; diff --git a/uniffi_core/src/ffi_converter_traits.rs b/uniffi_core/src/ffi_converter_traits.rs index 3b5914e32f..f9101011f1 100644 --- a/uniffi_core/src/ffi_converter_traits.rs +++ b/uniffi_core/src/ffi_converter_traits.rs @@ -51,7 +51,9 @@ use std::{borrow::Borrow, sync::Arc}; use anyhow::bail; use bytes::Buf; -use crate::{FfiDefault, MetadataBuffer, Result, RustBuffer, UnexpectedUniFFICallbackError}; +use crate::{ + FfiDefault, Handle, MetadataBuffer, Result, RustBuffer, UnexpectedUniFFICallbackError, +}; /// Generalized FFI conversions /// @@ -351,6 +353,23 @@ pub trait ConvertError: Sized { fn try_convert_unexpected_callback_error(e: UnexpectedUniFFICallbackError) -> Result; } +/// Store instances `Arc` in a [crate::Slab] and generate handles to pass to the foreign code +/// +/// ## Safety +/// +/// All traits are unsafe (implementing it requires `unsafe impl`) because we can't guarantee +/// that it's safe to pass your type out to foreign-language code and back again. Buggy +/// implementations of this trait might violate some assumptions made by the generated code, +/// or might not match with the corresponding code in the generated foreign-language bindings. +/// These traits should not be used directly, only in generated code, and the generated code should +/// have fixture tests to test that everything works correctly together. +/// `&T` using the Arc. +pub unsafe trait SlabAlloc { + fn insert(value: Arc) -> Handle; + fn get_clone(handle: Handle) -> Arc; + fn remove(handle: Handle) -> Arc; +} + /// Derive FFI traits /// /// This can be used to derive: @@ -463,4 +482,26 @@ macro_rules! derive_ffi_traits { } } }; + + (impl $(<$($generic:ident),*>)? $(::uniffi::)? SlabAlloc<$ut:path> for $ty:ty $(where $($where:tt)*)?) => { + // Using an anonymous const here creates a new namespace so that our `SLAB` static doesn't + // conflict with other names. + const _: () = { + $crate::static_slab!(SLAB, ::std::sync::Arc<$ty>); + + unsafe impl $(<$($generic),*>)* $crate::SlabAlloc<$ut> for $ty $(where $($where)*)* + { + fn insert(value: ::std::sync::Arc) -> $crate::Handle { + SLAB.insert_or_panic(value) + } + fn get_clone(handle: $crate::Handle) -> ::std::sync::Arc { + SLAB.get_clone_or_panic(handle) + } + + fn remove(handle: $crate::Handle) -> ::std::sync::Arc { + SLAB.remove_or_panic(handle).0 + } + } + }; + }; } diff --git a/uniffi_core/src/lib.rs b/uniffi_core/src/lib.rs index 9003b08f9b..1763b93cec 100644 --- a/uniffi_core/src/lib.rs +++ b/uniffi_core/src/lib.rs @@ -46,6 +46,7 @@ pub mod metadata; pub use ffi::*; pub use ffi_converter_traits::{ ConvertError, FfiConverter, FfiConverterArc, Lift, LiftRef, LiftReturn, Lower, LowerReturn, + SlabAlloc, }; pub use metadata::*; @@ -56,6 +57,7 @@ pub mod deps { #[cfg(feature = "tokio")] pub use async_compat; pub use bytes; + pub use const_random; pub use log; pub use static_assertions; } diff --git a/uniffi_macros/src/export/scaffolding.rs b/uniffi_macros/src/export/scaffolding.rs index d00d8403bd..8f5f1cb480 100644 --- a/uniffi_macros/src/export/scaffolding.rs +++ b/uniffi_macros/src/export/scaffolding.rs @@ -143,9 +143,7 @@ impl ScaffoldingBits { // pointer. quote! { { - let foreign_arc = ::std::boxed::Box::leak(unsafe { Box::from_raw(uniffi_self_lowered as *mut ::std::sync::Arc) }); - // Take a clone for our own use. - Ok(::std::sync::Arc::clone(foreign_arc)) + Ok(>::get_clone(uniffi_self_lowered)) } } } else { @@ -264,7 +262,7 @@ pub(super) fn gen_ffi_function( quote! { #[doc(hidden)] #[no_mangle] - pub extern "C" fn #ffi_ident(#(#params,)*) -> ::uniffi::RustFutureHandle { + pub extern "C" fn #ffi_ident(#(#params,)*) -> ::uniffi::Handle { ::uniffi::deps::log::debug!(#name); let uniffi_lift_args = #lift_closure; match uniffi_lift_args() { diff --git a/uniffi_macros/src/export/trait_interface.rs b/uniffi_macros/src/export/trait_interface.rs index c4755015dc..37e5120539 100644 --- a/uniffi_macros/src/export/trait_interface.rs +++ b/uniffi_macros/src/export/trait_interface.rs @@ -11,7 +11,7 @@ use crate::{ item::ImplItem, }, object::interface_meta_static_var, - util::{ident_to_string, tagged_impl_header}, + util::{derive_ffi_traits, ident_to_string, tagged_impl_header}, }; use uniffi_meta::free_fn_symbol_name; @@ -28,7 +28,6 @@ pub(super) fn gen_trait_scaffolding( let trait_name = ident_to_string(&self_ident); let trait_impl = callback_interface::trait_impl(mod_path, &self_ident, &items) .unwrap_or_else(|e| e.into_compile_error()); - let free_fn_ident = Ident::new( &free_fn_symbol_name(mod_path, &trait_name), Span::call_site(), @@ -38,12 +37,11 @@ pub(super) fn gen_trait_scaffolding( #[doc(hidden)] #[no_mangle] pub extern "C" fn #free_fn_ident( - ptr: *const ::std::ffi::c_void, + handle: ::uniffi::Handle, call_status: &mut ::uniffi::RustCallStatus ) { uniffi::rust_call(call_status, || { - assert!(!ptr.is_null()); - drop(unsafe { ::std::boxed::Box::from_raw(ptr as *mut std::sync::Arc) }); + >::remove(handle); Ok(()) }); } @@ -83,6 +81,8 @@ pub(super) fn gen_trait_scaffolding( pub(crate) fn ffi_converter(mod_path: &str, trait_ident: &Ident, udl_mode: bool) -> TokenStream { let impl_spec = tagged_impl_header("FfiConverterArc", "e! { dyn #trait_ident }, udl_mode); let lift_ref_impl_spec = tagged_impl_header("LiftRef", "e! { dyn #trait_ident }, udl_mode); + let derive_ffi_traits = + derive_ffi_traits("e! { dyn #trait_ident }, udl_mode, &["SlabAlloc"]); let trait_name = ident_to_string(trait_ident); let trait_impl_ident = callback_interface::trait_impl_ident(&trait_name); @@ -93,30 +93,31 @@ pub(crate) fn ffi_converter(mod_path: &str, trait_ident: &Ident, udl_mode: bool) // and thus help the user debug why the requirement isn't being met. uniffi::deps::static_assertions::assert_impl_all!(dyn #trait_ident: Sync, Send); + #derive_ffi_traits + unsafe #impl_spec { - type FfiType = *const ::std::os::raw::c_void; + type FfiType = ::uniffi::Handle; fn lower(obj: ::std::sync::Arc) -> Self::FfiType { - ::std::boxed::Box::into_raw(::std::boxed::Box::new(obj)) as *const ::std::os::raw::c_void + >::insert(obj) } fn try_lift(v: Self::FfiType) -> ::uniffi::deps::anyhow::Result<::std::sync::Arc> { - Ok(::std::sync::Arc::new(<#trait_impl_ident>::new(v as u64))) + Ok(::std::sync::Arc::new(<#trait_impl_ident>::new(v))) } fn write(obj: ::std::sync::Arc, buf: &mut Vec) { - ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8); - ::uniffi::deps::bytes::BufMut::put_u64( + ::uniffi::deps::bytes::BufMut::put_i64( buf, - >::lower(obj) as u64, + >::lower(obj).as_raw(), ); } fn try_read(buf: &mut &[u8]) -> ::uniffi::Result<::std::sync::Arc> { - ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8); ::uniffi::check_remaining(buf, 8)?; >::try_lift( - ::uniffi::deps::bytes::Buf::get_u64(buf) as Self::FfiType) + ::uniffi::Handle::from_raw(::uniffi::deps::bytes::Buf::get_i64(buf)) + ) } const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TYPE_INTERFACE) diff --git a/uniffi_macros/src/object.rs b/uniffi_macros/src/object.rs index 573a1eaadd..4355be88f3 100644 --- a/uniffi_macros/src/object.rs +++ b/uniffi_macros/src/object.rs @@ -3,7 +3,9 @@ use quote::quote; use syn::DeriveInput; use uniffi_meta::free_fn_symbol_name; -use crate::util::{create_metadata_items, ident_to_string, mod_path, tagged_impl_header}; +use crate::util::{ + create_metadata_items, derive_ffi_traits, ident_to_string, mod_path, tagged_impl_header, +}; pub fn expand_object(input: DeriveInput, udl_mode: bool) -> syn::Result { let module_path = mod_path()?; @@ -20,15 +22,11 @@ pub fn expand_object(input: DeriveInput, udl_mode: bool) -> syn::Result(); - unsafe { - ::std::sync::Arc::decrement_strong_count(ptr); - } + <#ident as ::uniffi::SlabAlloc>::remove(handle); Ok(()) }); } @@ -42,6 +40,7 @@ pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream { let name = ident_to_string(ident); let impl_spec = tagged_impl_header("FfiConverterArc", ident, udl_mode); let lift_ref_impl_spec = tagged_impl_header("LiftRef", ident, udl_mode); + let derive_ffi_traits = derive_ffi_traits(ident, udl_mode, &["SlabAlloc"]); let mod_path = match mod_path() { Ok(p) => p, Err(e) => return e.into_compile_error(), @@ -54,6 +53,8 @@ pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream { // and thus help the user debug why the requirement isn't being met. uniffi::deps::static_assertions::assert_impl_all!(#ident: Sync, Send); + #derive_ffi_traits + #[doc(hidden)] #[automatically_derived] /// Support for passing reference-counted shared objects via the FFI. @@ -62,8 +63,7 @@ pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream { /// by reference must be encapsulated in an `Arc`, and must be safe to share /// across threads. unsafe #impl_spec { - // Don't use a pointer to as that requires a `pub ` - type FfiType = *const ::std::os::raw::c_void; + type FfiType = ::uniffi::Handle; /// When lowering, we have an owned `Arc` and we transfer that ownership /// to the foreign-language code, "leaking" it out of Rust's ownership system @@ -75,7 +75,7 @@ pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream { /// call the destructor function specific to the type `T`. Calling the destructor /// function for other types may lead to undefined behaviour. fn lower(obj: ::std::sync::Arc) -> Self::FfiType { - ::std::sync::Arc::into_raw(obj) as Self::FfiType + <#ident as ::uniffi::SlabAlloc>::insert(obj) } /// When lifting, we receive a "borrow" of the `Arc` that is owned by @@ -84,11 +84,7 @@ pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream { /// Safety: the provided value must be a pointer previously obtained by calling /// the `lower()` or `write()` method of this impl. fn try_lift(v: Self::FfiType) -> ::uniffi::Result<::std::sync::Arc> { - let v = v as *const #ident; - // We musn't drop the `Arc` that is owned by the foreign-language code. - let foreign_arc = ::std::mem::ManuallyDrop::new(unsafe { ::std::sync::Arc::::from_raw(v) }); - // Take a clone for our own use. - Ok(::std::sync::Arc::clone(&*foreign_arc)) + Ok(<#ident as ::uniffi::SlabAlloc>::get_clone(v)) } /// When writing as a field of a complex structure, make a clone and transfer ownership @@ -100,8 +96,7 @@ pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream { /// call the destructor function specific to the type `T`. Calling the destructor /// function for other types may lead to undefined behaviour. fn write(obj: ::std::sync::Arc, buf: &mut Vec) { - ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8); - ::uniffi::deps::bytes::BufMut::put_u64(buf, >::lower(obj) as u64); + ::uniffi::deps::bytes::BufMut::put_i64(buf, >::lower(obj).as_raw()) } /// When reading as a field of a complex structure, we receive a "borrow" of the `Arc` @@ -110,9 +105,10 @@ pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream { /// Safety: the buffer must contain a pointer previously obtained by calling /// the `lower()` or `write()` method of this impl. fn try_read(buf: &mut &[u8]) -> ::uniffi::Result<::std::sync::Arc> { - ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8); ::uniffi::check_remaining(buf, 8)?; - >::try_lift(::uniffi::deps::bytes::Buf::get_u64(buf) as Self::FfiType) + >::try_lift( + ::uniffi::Handle::from_raw(::uniffi::deps::bytes::Buf::get_i64(buf)) + ) } const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TYPE_INTERFACE) diff --git a/uniffi_macros/src/setup_scaffolding.rs b/uniffi_macros/src/setup_scaffolding.rs index a21016e9ff..ebcd1dfd82 100644 --- a/uniffi_macros/src/setup_scaffolding.rs +++ b/uniffi_macros/src/setup_scaffolding.rs @@ -138,12 +138,12 @@ pub fn setup_scaffolding(namespace: String) -> Result { /// Generates the rust_future_* functions /// -/// The foreign side uses a type-erased `RustFutureHandle` to interact with futures, which presents +/// The foreign side uses a type-erased `Handle` to interact with futures, which presents /// a problem when creating scaffolding functions. What is the `ReturnType` parameter of `RustFutureFfi`? /// /// Handle this by using some brute-force monomorphization. For each possible ffi type, we /// generate a set of scaffolding functions. The bindings code is responsible for calling the one -/// corresponds the scaffolding function that created the `RustFutureHandle`. +/// corresponds the scaffolding function that created the `Handle`. /// /// This introduces safety issues, but we do get some type checking. If the bindings code calls /// the wrong rust_future_complete function, they should get an unexpected return type, which @@ -160,6 +160,7 @@ fn rust_future_scaffolding_fns(module_path: &str) -> TokenStream { (quote! { i64 }, "i64"), (quote! { f32 }, "f32"), (quote! { f64 }, "f64"), + (quote! { ::uniffi::Handle }, "handle"), (quote! { *const ::std::ffi::c_void }, "pointer"), (quote! { ::uniffi::RustBuffer }, "rust_buffer"), (quote! { () }, "void"), @@ -175,32 +176,32 @@ fn rust_future_scaffolding_fns(module_path: &str) -> TokenStream { #[allow(clippy::missing_safety_doc, missing_docs)] #[doc(hidden)] #[no_mangle] - pub unsafe extern "C" fn #ffi_rust_future_poll(handle: ::uniffi::RustFutureHandle, callback: ::uniffi::RustFutureContinuationCallback, data: *const ()) { - ::uniffi::ffi::rust_future_poll::<#return_type>(handle, callback, data); + pub unsafe extern "C" fn #ffi_rust_future_poll(handle: ::uniffi::Handle, callback: ::uniffi::RustFutureContinuationCallback, data: *const ()) { + ::uniffi::ffi::rust_future_poll::<#return_type, crate::UniFfiTag>(handle, callback, data); } #[allow(clippy::missing_safety_doc, missing_docs)] #[doc(hidden)] #[no_mangle] - pub unsafe extern "C" fn #ffi_rust_future_cancel(handle: ::uniffi::RustFutureHandle) { - ::uniffi::ffi::rust_future_cancel::<#return_type>(handle) + pub unsafe extern "C" fn #ffi_rust_future_cancel(handle: ::uniffi::Handle) { + ::uniffi::ffi::rust_future_cancel::<#return_type, crate::UniFfiTag>(handle) } #[allow(clippy::missing_safety_doc, missing_docs)] #[doc(hidden)] #[no_mangle] pub unsafe extern "C" fn #ffi_rust_future_complete( - handle: ::uniffi::RustFutureHandle, + handle: ::uniffi::Handle, out_status: &mut ::uniffi::RustCallStatus ) -> #return_type { - ::uniffi::ffi::rust_future_complete::<#return_type>(handle, out_status) + ::uniffi::ffi::rust_future_complete::<#return_type, crate::UniFfiTag>(handle, out_status) } #[allow(clippy::missing_safety_doc, missing_docs)] #[doc(hidden)] #[no_mangle] - pub unsafe extern "C" fn #ffi_rust_future_free(handle: ::uniffi::RustFutureHandle) { - ::uniffi::ffi::rust_future_free::<#return_type>(handle) + pub unsafe extern "C" fn #ffi_rust_future_free(handle: ::uniffi::Handle) { + ::uniffi::ffi::rust_future_free::<#return_type, crate::UniFfiTag>(handle) } } }) diff --git a/uniffi_macros/src/util.rs b/uniffi_macros/src/util.rs index 9f213ea1d7..9cfd554491 100644 --- a/uniffi_macros/src/util.rs +++ b/uniffi_macros/src/util.rs @@ -216,7 +216,7 @@ pub(crate) fn tagged_impl_header( } } -pub(crate) fn derive_all_ffi_traits(ty: &Ident, udl_mode: bool) -> TokenStream { +pub(crate) fn derive_all_ffi_traits(ty: impl ToTokens, udl_mode: bool) -> TokenStream { if udl_mode { quote! { ::uniffi::derive_ffi_traits!(local #ty); } } else { @@ -224,7 +224,11 @@ pub(crate) fn derive_all_ffi_traits(ty: &Ident, udl_mode: bool) -> TokenStream { } } -pub(crate) fn derive_ffi_traits(ty: &Ident, udl_mode: bool, trait_names: &[&str]) -> TokenStream { +pub(crate) fn derive_ffi_traits( + ty: impl ToTokens, + udl_mode: bool, + trait_names: &[&str], +) -> TokenStream { let trait_idents = trait_names .iter() .map(|name| Ident::new(name, Span::call_site()));