diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a8482ae49..86a6e255ed 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 handle system. ## v0.25.0 (backend crates: v0.25.0) - (_2023-10-18_) 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..2cf93f5cc2 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt @@ -1,3 +1,8 @@ +// Handles are defined as unsigned in Rust, but that's doesn't work well with JNA. +// We can pretend its signed since Rust handles are opaque values and Kotlin handles don't use all +// 64 bits. +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..fd496daca6 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_uint64".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..f9807618f9 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 => ":uint64".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..fde9022cdf 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 => "UInt64".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 => "uint64_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..668b38e79b 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: () -> UInt64, + pollFunc: (UInt64, @escaping UniFfiRustFutureContinuation, UnsafeMutableRawPointer) -> (), + completeFunc: (UInt64, UnsafeMutablePointer) -> F, + freeFunc: (UInt64) -> (), 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..5f566bdcab 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: UInt64 // 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: UInt64) { + 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 = UInt64 typealias SwiftType = {{ type_name }} - public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> {{ type_name }} { - return {{ impl_class_name }}(unsafeFromRawPointer: pointer) + public static func lift(_ handle: UInt64) throws -> {{ type_name }} { + return {{ impl_class_name }}(handle: handle) } - public static func lower(_ value: {{ type_name }}) -> UnsafeMutableRawPointer { + public static func lower(_ value: {{ type_name }}) -> UInt64 { {%- 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: UInt64) 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 }}) -> UInt64 { 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/src/ffi/rustfuture.rs b/uniffi_core/src/ffi/rustfuture.rs index 0c1a24174b..070b23847c 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, HandleAlloc, LowerReturn, + RustCallStatus, +}; /// 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: HandleAlloc, { // 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 HandleAlloc>::new_handle( + RustFuture::new(future, tag) as Arc> + ) } /// Poll a Rust future @@ -145,14 +137,15 @@ where /// /// # Safety /// -/// The [RustFutureHandle] must not previously have been passed to [rust_future_free] -pub unsafe fn rust_future_poll( - handle: RustFutureHandle, +/// The [Handle] must not previously have been passed to [rust_future_free] +pub unsafe fn rust_future_poll( + handle: Handle, callback: RustFutureContinuationCallback, data: *const (), -) { - let future = &*(handle.0 as *mut Arc>); - future.clone().ffi_poll(callback, data) +) where + dyn RustFutureFfi: HandleAlloc, +{ + as HandleAlloc>::get_arc(handle).ffi_poll(callback, data) } /// Cancel a Rust future @@ -164,10 +157,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: HandleAlloc, +{ + as HandleAlloc>::get_arc(handle).ffi_cancel() } /// Complete a Rust future @@ -177,15 +172,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: HandleAlloc, +{ + as HandleAlloc>::get_arc(handle).ffi_complete(out_status) } /// Free a Rust future, dropping the strong reference and releasing all references held by the @@ -193,12 +190,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: HandleAlloc, +{ + as HandleAlloc>::consume_handle(handle).ffi_free() } +// Derive HandleAlloc for dyn RustFutureFfi for all FFI return types +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi<()>); +derive_ffi_traits!(impl HandleAlloc 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 +297,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 +311,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 +390,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 +400,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 +425,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 +477,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 +503,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 +512,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_macros/src/export/scaffolding.rs b/uniffi_macros/src/export/scaffolding.rs index d00d8403bd..9a677ec3e4 100644 --- a/uniffi_macros/src/export/scaffolding.rs +++ b/uniffi_macros/src/export/scaffolding.rs @@ -151,7 +151,6 @@ impl ScaffoldingBits { } else { quote! { #lift_impl::try_lift(uniffi_self_lowered) } }; - let lift_closure = sig.lift_closure(Some(quote! { match #try_lift_self { Ok(v) => v, @@ -264,7 +263,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 53a7f78c17..a537a71349 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,12 @@ pub(super) fn gen_trait_scaffolding( #[doc(hidden)] #[no_mangle] pub extern "C" fn #free_fn_ident( - ptr: *const ::std::ffi::c_void, + handle: u64, 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) }); + let handle = ::uniffi::Handle::from_raw(handle).unwrap_or_else(|| panic!("{} free: null handle", #trait_name)); + >::consume_handle(handle); Ok(()) }); } @@ -83,6 +82,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, &["HandleAlloc"]); let trait_name = ident_to_string(trait_ident); let trait_impl_ident = callback_interface::trait_impl_ident(&trait_name); @@ -93,30 +94,33 @@ 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: ::core::marker::Sync, ::core::marker::Send); + #derive_ffi_traits + unsafe #impl_spec { - type FfiType = *const ::std::os::raw::c_void; + type FfiType = u64; 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 + >::new_handle(obj).as_raw() } 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))) + let handle = match ::uniffi::Handle::from_raw(v) { + Some(h) => h, + None => ::uniffi::deps::anyhow::bail!("{}::try_lift: null handle", #trait_name), + }; + Ok(::std::sync::Arc::new(<#trait_impl_ident>::new(handle))) } 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, + >::lower(obj) ); } 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::deps::bytes::Buf::get_u64(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 697641c829..114183019e 100644 --- a/uniffi_macros/src/object.rs +++ b/uniffi_macros/src/object.rs @@ -20,15 +20,12 @@ pub fn expand_object(input: DeriveInput, udl_mode: bool) -> syn::Result(); - unsafe { - ::std::sync::Arc::decrement_strong_count(ptr); - } + let handle = ::uniffi::Handle::from_raw(handle).unwrap_or_else(|| panic!("{} free: null handle", #name)); + <#ident as ::uniffi::HandleAlloc>::consume_handle(handle); Ok(()) }); } @@ -58,61 +55,30 @@ pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream { #[automatically_derived] /// Support for passing reference-counted shared objects via the FFI. /// - /// To avoid dealing with complex lifetime semantics over the FFI, any data passed - /// by reference must be encapsulated in an `Arc`, and must be safe to share - /// across threads. + /// Objects are `Arc<>` values in Rust and Handle values on the FFI. + /// The `HandleAlloc` trait doc string has usage guidelines for handles. unsafe #impl_spec { - // Don't use a pointer to as that requires a `pub ` - type FfiType = *const ::std::os::raw::c_void; + type FfiType = u64; - /// 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 - /// as a raw pointer. This works safely because we have unique ownership of `self`. - /// The foreign-language code is responsible for freeing this by calling the - /// `ffi_object_free` FFI function provided by the corresponding UniFFI type. - /// - /// Safety: when freeing the resulting pointer, the foreign-language code must - /// 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::HandleAlloc>::new_handle(obj).as_raw() } - /// When lifting, we receive a "borrow" of the `Arc` that is owned by - /// the foreign-language code, and make a clone of it for our own use. - /// - /// 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)) + let handle = match ::uniffi::Handle::from_raw(v) { + Some(h) => h, + None => ::uniffi::deps::anyhow::bail!("{}::try_lift: null handle", #name), + }; + Ok(<#ident as ::uniffi::HandleAlloc>::get_arc(handle)) } - /// When writing as a field of a complex structure, make a clone and transfer ownership - /// of it to the foreign-language code by writing its pointer into the buffer. - /// The foreign-language code is responsible for freeing this by calling the - /// `ffi_object_free` FFI function provided by the corresponding UniFFI type. - /// - /// Safety: when freeing the resulting pointer, the foreign-language code must - /// 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_u64(buf, >::lower(obj)) } - /// When reading as a field of a complex structure, we receive a "borrow" of the `Arc` - /// that is owned by the foreign-language code, and make a clone for our own use. - /// - /// 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::deps::bytes::Buf::get_u64(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()));