diff --git a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs index be16218aaf..aa9d13fe76 100644 --- a/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs +++ b/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs @@ -370,6 +370,7 @@ impl KotlinCodeOracle { FfiType::Int64 | FfiType::UInt64 => "Long".to_string(), FfiType::Float32 => "Float".to_string(), FfiType::Float64 => "Double".to_string(), + FfiType::Handle => "Long".to_string(), FfiType::RustArcPtr(_) => "Pointer".to_string(), FfiType::RustBuffer(maybe_suffix) => { format!("RustBuffer{}", maybe_suffix.as_deref().unwrap_or_default()) @@ -378,7 +379,7 @@ impl KotlinCodeOracle { FfiType::Callback(name) => self.ffi_callback_name(name), FfiType::Struct(name) => self.ffi_struct_name(name), FfiType::Reference(inner) => self.ffi_type_label_by_reference(inner), - FfiType::VoidPointer | FfiType::RustFutureHandle => "Pointer".to_string(), + FfiType::VoidPointer => "Pointer".to_string(), } } diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt b/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt index 33fdff69b5..0c9c8053c4 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: UniffiRustFutureContinuati } internal suspend fun uniffiRustCallAsync( - rustFuture: Pointer, - pollFunc: (Pointer, UniffiRustFutureContinuationCallback, Long) -> Unit, - completeFunc: (Pointer, UniffiRustCallStatus) -> F, - freeFunc: (Pointer) -> Unit, + rustFuture: Long, + pollFunc: (Long, UniffiRustFutureContinuationCallback, Long) -> Unit, + completeFunc: (Long, UniffiRustCallStatus) -> F, + freeFunc: (Long) -> Unit, liftFunc: (F) -> T, errorHandler: UniffiRustCallStatusErrorHandler ): T { diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt b/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt index 6bd6c93fb9..81b0493d65 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt @@ -6,14 +6,14 @@ internal const val UNIFFI_CALLBACK_SUCCESS = 0 internal const val UNIFFI_CALLBACK_ERROR = 1 internal const val UNIFFI_CALLBACK_UNEXPECTED_ERROR = 2 -public abstract class FfiConverterCallbackInterface: FfiConverter { +public abstract class FfiConverterCallbackInterface: FfiConverter { internal val handleMap = UniffiHandleMap() - internal fun drop(handle: UniffiHandle) { + internal fun drop(handle: Long) { handleMap.remove(handle) } - override fun lift(value: UniffiHandle): CallbackInterface { + override fun lift(value: Long): CallbackInterface { return handleMap.get(value) } diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt index 5285cbc4ab..d2cdee4f33 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt @@ -9,5 +9,5 @@ {% include "Interface.kt" %} {% include "CallbackInterfaceImpl.kt" %} -// The ffiConverter which transforms the Callbacks in to UniffiHandles to pass to Rust. +// The ffiConverter which transforms the Callbacks in to handles to pass to Rust. public object {{ ffi_converter_name }}: FfiConverterCallbackInterface<{{ interface_name }}>() diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt b/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt index a06c4468ba..3a56648190 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt @@ -1,30 +1,27 @@ -// Handle from a UniffiHandleMap -internal typealias UniffiHandle = Long - // Map handles to objects // // This is used pass an opaque 64-bit handle representing a foreign object to the Rust code. internal class UniffiHandleMap { - private val map = ConcurrentHashMap() + private val map = ConcurrentHashMap() private val counter = java.util.concurrent.atomic.AtomicLong(0) val size: Int get() = map.size // Insert a new object into the handle map and get a handle for it - fun insert(obj: T): UniffiHandle { + fun insert(obj: T): Long { val handle = counter.getAndAdd(1) map.put(handle, obj) return handle } // Get an object from the handle map - fun get(handle: UniffiHandle): T { + fun get(handle: Long): T { return map.get(handle) ?: throw InternalException("UniffiHandleMap.get: Invalid handle") } // Remove an entry from the handlemap and get the Kotlin object back - fun remove(handle: UniffiHandle): T { + fun remove(handle: Long): T { return map.remove(handle) ?: throw InternalException("UniffiHandleMap: Invalid handle") } } diff --git a/uniffi_bindgen/src/bindings/python/gen_python/mod.rs b/uniffi_bindgen/src/bindings/python/gen_python/mod.rs index d25314659e..6c1996eb5c 100644 --- a/uniffi_bindgen/src/bindings/python/gen_python/mod.rs +++ b/uniffi_bindgen/src/bindings/python/gen_python/mod.rs @@ -361,6 +361,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::Handle => "ctypes.c_uint64".to_string(), FfiType::RustArcPtr(_) => "ctypes.c_void_p".to_string(), FfiType::RustBuffer(maybe_suffix) => match maybe_suffix { Some(suffix) => format!("_UniffiRustBuffer{suffix}"), @@ -371,7 +372,7 @@ impl PythonCodeOracle { FfiType::Struct(name) => self.ffi_struct_name(name), // Pointer to an `asyncio.EventLoop` instance FfiType::Reference(inner) => format!("ctypes.POINTER({})", self.ffi_type_label(inner)), - FfiType::VoidPointer | FfiType::RustFutureHandle => "ctypes.c_void_p".to_string(), + FfiType::VoidPointer => "ctypes.c_void_p".to_string(), } } diff --git a/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs b/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs index a2d6d41247..07da3882b6 100644 --- a/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs +++ b/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs @@ -149,6 +149,7 @@ mod filters { FfiType::UInt64 => ":uint64".to_string(), FfiType::Float32 => ":float".to_string(), FfiType::Float64 => ":double".to_string(), + FfiType::Handle => ":uint64".to_string(), FfiType::RustArcPtr(_) => ":pointer".to_string(), FfiType::RustBuffer(_) => "RustBuffer.by_value".to_string(), FfiType::ForeignBytes => "ForeignBytes".to_string(), @@ -162,9 +163,6 @@ mod filters { FfiType::Struct(_) => { unimplemented!("Structs are not implemented") } - FfiType::RustFutureHandle => { - unimplemented!("Async functions are not implemented") - } }) } diff --git a/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs b/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs index 6f1464efe7..3969f6094e 100644 --- a/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs +++ b/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs @@ -529,6 +529,7 @@ impl SwiftCodeOracle { FfiType::UInt64 => "UInt64".into(), FfiType::Float32 => "Float".into(), FfiType::Float64 => "Double".into(), + FfiType::Handle => "UInt64".into(), FfiType::RustArcPtr(_) => "UnsafeMutableRawPointer".into(), FfiType::RustBuffer(_) => "RustBuffer".into(), FfiType::ForeignBytes => "ForeignBytes".into(), @@ -537,7 +538,7 @@ impl SwiftCodeOracle { FfiType::Reference(inner) => { format!("UnsafeMutablePointer<{}>", self.ffi_type_label(inner)) } - FfiType::VoidPointer | FfiType::RustFutureHandle => "UnsafeMutableRawPointer".into(), + FfiType::VoidPointer => "UnsafeMutableRawPointer".into(), } } @@ -630,6 +631,7 @@ pub mod filters { FfiType::UInt64 => "uint64_t".into(), FfiType::Float32 => "float".into(), FfiType::Float64 => "double".into(), + FfiType::Handle => "uint64_t".into(), FfiType::RustArcPtr(_) => "void*_Nonnull".into(), FfiType::RustBuffer(_) => "RustBuffer".into(), FfiType::ForeignBytes => "ForeignBytes".into(), @@ -638,7 +640,7 @@ pub mod filters { } FfiType::Struct(name) => SwiftCodeOracle.ffi_struct_name(name), FfiType::Reference(inner) => format!("{}* _Nonnull", header_ffi_type_name(inner)?), - FfiType::VoidPointer | FfiType::RustFutureHandle => "void* _Nonnull".into(), + FfiType::VoidPointer => "void* _Nonnull".into(), }) } diff --git a/uniffi_bindgen/src/bindings/swift/templates/Async.swift b/uniffi_bindgen/src/bindings/swift/templates/Async.swift index b91e264fe8..761e0dd70e 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/Async.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/Async.swift @@ -4,10 +4,10 @@ private let UNIFFI_RUST_FUTURE_POLL_MAYBE_READY: Int8 = 1 fileprivate let uniffiContinuationHandleMap = UniffiHandleMap>() fileprivate func uniffiRustCallAsync( - rustFutureFunc: () -> UnsafeMutableRawPointer, - pollFunc: (UnsafeMutableRawPointer, @escaping UniffiRustFutureContinuationCallback, UInt64) -> (), - completeFunc: (UnsafeMutableRawPointer, UnsafeMutablePointer) -> F, - freeFunc: (UnsafeMutableRawPointer) -> (), + rustFutureFunc: () -> UInt64, + pollFunc: (UInt64, @escaping UniffiRustFutureContinuationCallback, UInt64) -> (), + 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/CallbackInterfaceTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift index 1827dc19bf..7aa1cca9b2 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift @@ -18,19 +18,18 @@ fileprivate struct {{ ffi_converter_name }} { extension {{ ffi_converter_name }} : FfiConverter { typealias SwiftType = {{ type_name }} - // We can use Handle as the FfiType because it's a typealias to UInt64 - typealias FfiType = UniffiHandle + typealias FfiType = UInt64 - public static func lift(_ handle: UniffiHandle) throws -> SwiftType { + public static func lift(_ handle: UInt64) throws -> SwiftType { try handleMap.get(handle: handle) } public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { - let handle: UniffiHandle = try readInt(&buf) + let handle: UInt64 = try readInt(&buf) return try lift(handle) } - public static func lower(_ v: SwiftType) -> UniffiHandle { + public static func lower(_ v: SwiftType) -> UInt64 { return handleMap.insert(obj: v) } diff --git a/uniffi_bindgen/src/bindings/swift/templates/HandleMap.swift b/uniffi_bindgen/src/bindings/swift/templates/HandleMap.swift index c636ca6be6..af0305872b 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/HandleMap.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/HandleMap.swift @@ -1,10 +1,9 @@ -fileprivate typealias UniffiHandle = UInt64 fileprivate class UniffiHandleMap { - private var map: [UniffiHandle: T] = [:] + private var map: [UInt64: T] = [:] private let lock = NSLock() - private var currentHandle: UniffiHandle = 1 + private var currentHandle: UInt64 = 1 - func insert(obj: T) -> UniffiHandle { + func insert(obj: T) -> UInt64 { lock.withLock { let handle = currentHandle currentHandle += 1 @@ -13,7 +12,7 @@ fileprivate class UniffiHandleMap { } } - func get(handle: UniffiHandle) throws -> T { + func get(handle: UInt64) throws -> T { try lock.withLock { guard let obj = map[handle] else { throw UniffiInternalError.unexpectedStaleHandle @@ -23,7 +22,7 @@ fileprivate class UniffiHandleMap { } @discardableResult - func remove(handle: UniffiHandle) throws -> T { + func remove(handle: UInt64) throws -> T { try lock.withLock { guard let obj = map.removeValue(forKey: handle) else { throw UniffiInternalError.unexpectedStaleHandle diff --git a/uniffi_bindgen/src/interface/ffi.rs b/uniffi_bindgen/src/interface/ffi.rs index 44b09d3fea..5b3efdbb67 100644 --- a/uniffi_bindgen/src/interface/ffi.rs +++ b/uniffi_bindgen/src/interface/ffi.rs @@ -53,8 +53,10 @@ pub enum FfiType { /// Pointer to a FFI struct (e.g. a VTable). The inner value matches one of the items in /// [crate::ComponentInterface::ffi_struct_definitions]. Struct(String), - /// Pointer to a Rust future - RustFutureHandle, + /// Opaque 64-bit handle + /// + /// These are used to pass objects across the FFI. + Handle, /// Pointer to an FfiType. Reference(Box), /// Opaque pointer @@ -202,7 +204,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 375f7c0ea2..fe90fd5a14 100644 --- a/uniffi_bindgen/src/interface/mod.rs +++ b/uniffi_bindgen/src/interface/mod.rs @@ -514,7 +514,7 @@ impl ComponentInterface { arguments: vec![ FfiArgument { name: "handle".to_owned(), - type_: FfiType::RustFutureHandle, + type_: FfiType::Handle, }, FfiArgument { name: "callback".to_owned(), @@ -522,7 +522,7 @@ impl ComponentInterface { }, FfiArgument { name: "callback_data".to_owned(), - type_: FfiType::UInt64, + type_: FfiType::Handle, }, ], return_type: None, @@ -540,7 +540,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, @@ -555,7 +555,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, @@ -570,7 +570,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, diff --git a/uniffi_core/src/ffi/ffidefault.rs b/uniffi_core/src/ffi/ffidefault.rs index d3ca9438d8..97f12fb38f 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::default() + } +} + impl FfiDefault for *const std::ffi::c_void { fn ffi_default() -> Self { std::ptr::null() diff --git a/uniffi_core/src/ffi/handle.rs b/uniffi_core/src/ffi/handle.rs new file mode 100644 index 0000000000..8ee2f46c35 --- /dev/null +++ b/uniffi_core/src/ffi/handle.rs @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/// Object handle +/// +/// Handles opaque `u64` values used to pass objects across the FFI, both for objects implemented in +/// Rust and ones implemented in the foreign language. +/// +/// Rust handles are generated by leaking a raw pointer +/// Foreign handles are generated with a handle map that only generates odd values. +/// For all currently supported architectures and hopefully any ones we add in the future: +/// * 0 is an invalid value. +/// * The lowest bit will always be set for foreign handles and never set for Rust ones (since the +/// leaked pointer will be aligned). +/// +/// Rust handles are mainly managed is through the [crate::HandleAlloc] trait. +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] +#[repr(transparent)] +pub struct Handle(u64); + +impl Handle { + pub fn from_pointer(ptr: *const T) -> Self { + Self(ptr as u64) + } + + pub fn as_pointer(&self) -> *const T { + self.0 as *const T + } + + pub fn from_raw(raw: u64) -> Option { + if raw == 0 { + None + } else { + Some(Self(raw)) + } + } + + pub fn from_raw_unchecked(raw: u64) -> Self { + Self(raw) + } + + pub fn as_raw(&self) -> u64 { + self.0 + } +} diff --git a/uniffi_core/src/ffi/mod.rs b/uniffi_core/src/ffi/mod.rs index 24b9bbaa84..8e26be37b0 100644 --- a/uniffi_core/src/ffi/mod.rs +++ b/uniffi_core/src/ffi/mod.rs @@ -8,6 +8,7 @@ pub mod callbackinterface; pub mod ffidefault; pub mod foreignbytes; pub mod foreigncallbacks; +pub mod handle; pub mod rustbuffer; pub mod rustcalls; pub mod rustfuture; @@ -16,6 +17,7 @@ pub use callbackinterface::*; pub use ffidefault::FfiDefault; pub use foreignbytes::*; pub use foreigncallbacks::*; +pub use handle::*; pub use rustbuffer::*; pub use rustcalls::*; pub use rustfuture::*; diff --git a/uniffi_core/src/ffi/rustfuture/future.rs b/uniffi_core/src/ffi/rustfuture/future.rs index a42729bf36..93c34e7543 100644 --- a/uniffi_core/src/ffi/rustfuture/future.rs +++ b/uniffi_core/src/ffi/rustfuture/future.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 @@ -288,7 +288,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)] -pub trait RustFutureFfi { +pub trait RustFutureFfi: Send + Sync { fn ffi_poll(self: Arc, callback: RustFutureContinuationCallback, data: u64); fn ffi_cancel(&self); fn ffi_complete(&self, call_status: &mut RustCallStatus) -> ReturnType; diff --git a/uniffi_core/src/ffi/rustfuture/mod.rs b/uniffi_core/src/ffi/rustfuture/mod.rs index 3aec88a2a8..39529f2db1 100644 --- a/uniffi_core/src/ffi/rustfuture/mod.rs +++ b/uniffi_core/src/ffi/rustfuture/mod.rs @@ -12,7 +12,7 @@ use scheduler::*; #[cfg(test)] mod tests; -use crate::{LowerReturn, RustCallStatus}; +use crate::{derive_ffi_traits, Handle, HandleAlloc, LowerReturn, RustCallStatus}; /// Result code for [rust_future_poll]. This is passed to the continuation function. #[repr(i8)] @@ -30,17 +30,13 @@ pub enum RustFuturePoll { /// to continue progress on the future. pub type RustFutureContinuationCallback = extern "C" fn(callback_data: u64, 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 Rust future /// /// 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 @@ -52,15 +48,12 @@ where T: LowerReturn + Send + 'static, // The UniFfiTag ZST. The Send + 'static bound is to keep rustc happy. UT: Send + 'static, + // Needed to allocate 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 @@ -71,14 +64,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: u64, -) { - 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 @@ -90,10 +84,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 @@ -103,15 +99,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 @@ -119,8 +117,25 @@ 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>::get_arc(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<*const std::ffi::c_void>); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi); +derive_ffi_traits!(impl HandleAlloc for dyn RustFutureFfi<()>); diff --git a/uniffi_core/src/ffi_converter_traits.rs b/uniffi_core/src/ffi_converter_traits.rs index 987a47c414..4e7b9e06fa 100644 --- a/uniffi_core/src/ffi_converter_traits.rs +++ b/uniffi_core/src/ffi_converter_traits.rs @@ -52,7 +52,7 @@ use anyhow::bail; use bytes::Buf; use crate::{ - FfiDefault, MetadataBuffer, Result, RustBuffer, RustCallStatus, RustCallStatusCode, + FfiDefault, Handle, MetadataBuffer, Result, RustBuffer, RustCallStatus, RustCallStatusCode, UnexpectedUniFFICallbackError, }; @@ -381,6 +381,66 @@ pub trait ConvertError: Sized { fn try_convert_unexpected_callback_error(e: UnexpectedUniFFICallbackError) -> Result; } +/// Manage handles for `Arc` instances +/// +/// Handles are used to manage objects that are passed across the FFI. They general usage is: +/// +/// * Rust creates an `Arc<>` +/// * Rust uses `new_handle` to create a handle that represents the Arc reference +/// * Rust passes the handle to the foreign code as a `u64` +/// * The foreign code passes the handle back to `Rust` to refer to the object: +/// * Handle are usually passed as borrowed values. When an FFI function inputs a handle as an +/// argument, the foreign code simply passes a copy of the `u64` to Rust, which calls `get_arc` +/// to get a new `Arc<>` clone for it. +/// * Handles are returned as owned values. When an FFI function returns a handle, the foreign +/// code either stops using the handle after returning it or calls `clone_handle` and returns +/// the clone. +/// * Eventually the foreign code may destroy their handle by passing it into a "free" FFI +/// function. This functions input an owned handle and consume it. +/// +/// The foreign code also defines their own handles. These represent foreign objects that are +/// passed to Rust. Using foreign handles is essentially the same as above, but in reverse. +/// +/// Handles must always be `Send` and the objects they reference must always be `Sync`. +/// This means that it must be safe to send handles to other threads and use them there. +/// +/// Note: this only needs to be derived for unsized types, there's a blanket impl for `T: Sized`. +/// +/// ## 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 HandleAlloc: Send + Sync { + /// Create a new handle for an Arc value + /// + /// Use this to lower an Arc into a handle value before passing it across the FFI. + /// The newly-created handle will have reference count = 1. + fn new_handle(value: Arc) -> Handle; + + /// Clone a handle + /// + /// This creates a new handle from an existing one. + /// It's used when the foreign code wants to pass back an owned handle and still keep a copy + /// for themselves. + fn clone_handle(handle: Handle) -> Handle; + + /// Get a clone of the `Arc<>` using a "borrowed" handle. + /// + /// Take care that the handle can not be destroyed between when it's passed and when + /// `get_arc()` is called. #1797 is a cautionary tale. + fn get_arc(handle: Handle) -> Arc { + Self::consume_handle(Self::clone_handle(handle)) + } + + /// Consume a handle, getting back the initial `Arc<>` + fn consume_handle(handle: Handle) -> Arc; +} + /// Derive FFI traits /// /// This can be used to derive: @@ -494,4 +554,50 @@ macro_rules! derive_ffi_traits { } } }; + + (impl $(<$($generic:ident),*>)? $(::uniffi::)? HandleAlloc<$ut:path> for $ty:ty $(where $($where:tt)*)?) => { + // Derived HandleAlloc implementation. + // + // This is only needed for !Sized types like `dyn Trait`, below is a blanket implementation + // for any sized type. + unsafe impl $(<$($generic),*>)* $crate::HandleAlloc<$ut> for $ty $(where $($where)*)* + { + // To implement HandleAlloc for an unsized type, wrap it with a second Arc which + // converts the wide pointer into a normal pointer. + + fn new_handle(value: ::std::sync::Arc) -> $crate::Handle { + $crate::Handle::from_pointer(::std::sync::Arc::into_raw(::std::sync::Arc::new(value))) + } + + fn clone_handle(handle: $crate::Handle) -> $crate::Handle { + unsafe { + ::std::sync::Arc::<::std::sync::Arc>::increment_strong_count(handle.as_pointer::<::std::sync::Arc>()); + } + handle + } + + fn consume_handle(handle: $crate::Handle) -> ::std::sync::Arc { + unsafe { + ::std::sync::Arc::::clone( + &std::sync::Arc::<::std::sync::Arc::>::from_raw(handle.as_pointer::<::std::sync::Arc>()) + ) + } + } + } + }; +} + +unsafe impl HandleAlloc for T { + fn new_handle(value: Arc) -> Handle { + Handle::from_pointer(Arc::into_raw(value)) + } + + fn clone_handle(handle: Handle) -> Handle { + unsafe { Arc::increment_strong_count(handle.as_pointer::()) }; + handle + } + + fn consume_handle(handle: Handle) -> Arc { + unsafe { Arc::from_raw(handle.as_pointer()) } + } } diff --git a/uniffi_core/src/lib.rs b/uniffi_core/src/lib.rs index 9003b08f9b..3ae2983eab 100644 --- a/uniffi_core/src/lib.rs +++ b/uniffi_core/src/lib.rs @@ -45,7 +45,8 @@ pub mod metadata; pub use ffi::*; pub use ffi_converter_traits::{ - ConvertError, FfiConverter, FfiConverterArc, Lift, LiftRef, LiftReturn, Lower, LowerReturn, + ConvertError, FfiConverter, FfiConverterArc, HandleAlloc, Lift, LiftRef, LiftReturn, Lower, + LowerReturn, }; pub use metadata::*; diff --git a/uniffi_macros/src/export/scaffolding.rs b/uniffi_macros/src/export/scaffolding.rs index 0e0da87a2e..31c4e83232 100644 --- a/uniffi_macros/src/export/scaffolding.rs +++ b/uniffi_macros/src/export/scaffolding.rs @@ -264,7 +264,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/setup_scaffolding.rs b/uniffi_macros/src/setup_scaffolding.rs index 58bb1ee483..08bb2cc568 100644 --- a/uniffi_macros/src/setup_scaffolding.rs +++ b/uniffi_macros/src/setup_scaffolding.rs @@ -129,12 +129,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 @@ -166,32 +166,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: u64) { - ::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: u64) { + ::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) } } })