Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for async functions returning opaque Rust types #120

Closed
josephg opened this issue Dec 8, 2022 · 3 comments
Closed

Add support for async functions returning opaque Rust types #120

josephg opened this issue Dec 8, 2022 · 3 comments
Labels
good first issue Good for newcomers

Comments

@josephg
Copy link

josephg commented Dec 8, 2022

I've got a quite complicated type I'm sharing via the swift bridge interface. I'm using an opaque type, but I want to use the async function bridge and it looks like thats not supported:

   Compiling dt-swift v0.1.0 (/Users/seph/src/diamond-types/crates/dt-swift)
error: failed to run custom build command for `dt-swift v0.1.0 (/Users/seph/src/diamond-types/crates/dt-swift)`

Caused by:
  process didn't exit successfully: `/Users/seph/src/diamond-types/target/release/build/dt-swift-82fb4d8c023013ad/build-script-build` (exit status: 101)
  --- stdout
  cargo:rerun-if-changed=src/lib.rs

  --- stderr
  thread 'main' panicked at 'not implemented', /Users/seph/.cargo/registry/src/github.com-1ecc6299db9ec823/swift-bridge-ir-0.1.41/src/bridged_type/bridged_opaque_type.rs:71:21

The code for async functions in opaque types is unimplemented:

                TypePosition::SwiftCallsRustAsyncOnCompleteReturnTy => {
                    unimplemented!()
                }
  • What is needed to add support here?
  • Are there any workarounds? Can I have a bridged struct with an opaque reference or something?
@chinedufn
Copy link
Owner

chinedufn commented Dec 8, 2022

Yup, having async functions return opaque Rust types is not yet supported.

What is needed to add support here?

We'd first want to add an integration test, similar to the one for returning returning a transparent struct from an async function.

async fn rust_async_return_struct() -> AsyncRustFnReturnStruct;

async fn rust_async_return_struct() -> ffi::AsyncRustFnReturnStruct {
ffi::AsyncRustFnReturnStruct
}

func testSwiftCallsRustAsyncFnRetStruct() async throws {
let _: AsyncRustFnReturnStruct = await rust_async_return_struct()
}


Next we'd add a codegen test for returning an opaque Rust type from an async function. Similar to:

/// Verify that we generate the correct code for extern "Rust" async functions that returns a struct.
mod extern_rust_async_function_returns_struct {
use super::*;
fn bridge_module() -> TokenStream {
quote! {
#[swift_bridge::bridge]
mod ffi {
struct SomeStruct;
extern "Rust" {
async fn some_function() -> SomeStruct;
}
}
}
}
fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
pub extern "C" fn __swift_bridge__some_function(
callback_wrapper: *mut std::ffi::c_void,
callback: extern "C" fn(*mut std::ffi::c_void, __swift_bridge__SomeStruct) -> (),
) {
let callback_wrapper = swift_bridge::async_support::SwiftCallbackWrapper(callback_wrapper);
let fut = super::some_function();
let task = async move {
let val = fut.await.into_ffi_repr();
let callback_wrapper = callback_wrapper;
let callback_wrapper = callback_wrapper.0;
(callback)(callback_wrapper, val)
};
swift_bridge::async_support::ASYNC_RUNTIME.spawn_task(Box::pin(task))
}
})
}
fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
func some_function() async -> SomeStruct {
func onComplete(cbWrapperPtr: UnsafeMutableRawPointer?, rustFnRetVal: __swift_bridge__$SomeStruct) {
let wrapper = Unmanaged<CbWrapper$some_function>.fromOpaque(cbWrapperPtr!).takeRetainedValue()
wrapper.cb(.success(rustFnRetVal.intoSwiftRepr()))
}
return await withCheckedContinuation({ (continuation: CheckedContinuation<SomeStruct, Never>) in
let callback = { rustFnRetVal in
continuation.resume(with: rustFnRetVal)
}
let wrapper = CbWrapper$some_function(cb: callback)
let wrapperPtr = Unmanaged.passRetained(wrapper).toOpaque()
__swift_bridge__$some_function(wrapperPtr, onComplete)
})
}
class CbWrapper$some_function {
var cb: (Result<SomeStruct, Never>) -> ()
public init(cb: @escaping (Result<SomeStruct, Never>) -> ()) {
self.cb = cb
}
}
"#,
)
}


Then we'd get the codegen tests passing.

In this case this would boil down to tweaking code in bridged_type.rs until the test passed.

pub(crate) trait BridgeableType: Debug {


After that we would verify that the integration test was passing by running ./test-integration.sh.


I just added some basic documentation on adding support for types / function signatures:

https://chinedufn.github.io/swift-bridge/contributing/adding-support-for-a-signature/index.html

Are there any workarounds? Can I have a bridged struct with an opaque reference or something?

That should work, but we haven't yet added explicit tests for struct SomeTransparentStruct { field: SomeOpaqueRustType } so I can't say with certainty. So far we only explicitly support primitive fields.

#[swift_bridge(swift_repr = "struct")]
struct StructReprStructWithOnePrimitiveField {
named_field: u8,
}

... but... it should work since the code for bridging struct primitive struct fields hits the same code paths as bridging any other type..

@chinedufn chinedufn added the good first issue Good for newcomers label Jan 27, 2023
@chinedufn chinedufn changed the title Add support for async functions returning opaque types Add support for async functions returning opaque Rust types Jan 27, 2023
@chinedufn
Copy link
Owner

chinedufn commented Feb 9, 2023

Related implementation for async fn some_function() -> Result<OpaqueRust, OpaqueRust>

#158

And the corresponding issue

#157

@chinedufn
Copy link
Owner

This was implemented in c4a11f4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
good first issue Good for newcomers
Projects
None yet
Development

No branches or pull requests

2 participants