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

Returning Result<struct, struct> #168

Open
robinwit opened this issue Feb 13, 2023 · 10 comments
Open

Returning Result<struct, struct> #168

robinwit opened this issue Feb 13, 2023 · 10 comments
Labels
good first issue Good for newcomers

Comments

@robinwit
Copy link

This seems to be supported, but I'm getting this error:

error[E0605]: non-primitive cast: `__swift_bridge__MyError` as `*mut c_void`
 --> src/lib.rs:6:1
  |
6 | #[swift_bridge::bridge]
  | ^^^^^^^^^^^^^^^^^^^^^^^ an `as` expression can only be used to convert between primitive types or to coerce to a specific trait object
  |
  = note: this error originates in the attribute macro `swift_bridge::bridge` (in Nightly builds, run with -Z macro-backtrace for more info)

Source:

#![feature(extern_types)]

use crate::ffi::*;
use std::collections::HashMap;

#[swift_bridge::bridge]
mod ffi {
    #[swift_bridge(swift_repr = "struct")]
    pub struct MyIpAddress {
        pub origin: String,
    }

    #[swift_bridge(swift_repr = "struct")]
    pub struct MyError {}

    extern "Rust" {
        // type MyIpAddress;
        // type MyError;

        fn get_my_ip_from_rust() -> Result<MyIpAddress, MyError>;
    }
}

fn get_my_ip_from_rust() -> Result<MyIpAddress, MyError> {
    println!("Starting HTTP request from the Rust side...");

    let origin = reqwest::blocking::get("https://httpbin.org/ip")
        .unwrap()
        .json::<HashMap<String, String>>()
        .unwrap()
        .remove("origin")
        .unwrap();

    println!("HTTP request complete. Returning the value to Swift...");

    Ok(MyIpAddress { origin })
}

Am I missing something?

@chinedufn
Copy link
Owner

chinedufn commented Feb 13, 2023

Result<T, E> for structs isn't supported yet since we currently only support Result for types that are passed over FFI via pointers (such as opaque Rust types).

I think that this will be mostly solved by #159 , since it will let us dynamically generate the FFI glue that we need for any supported T and E.


In short, this doesn't work yet, but should be easy to support after #159 lands. So feel free to subscribe to that issue.

I'll leave this issue open until we can confirm that it is resolved.

@robinwit
Copy link
Author

Nice, thanks for the update! Wasn't sure that would fix it. :)

@NiwakaDev
Copy link
Collaborator

NiwakaDev commented Mar 8, 2023

We have added support returning Result<enum, enum> from Rust functions.

So, I guess that we could easily implement returning Result<struct, struct> now.

@NiwakaDev
Copy link
Collaborator

I'll address this issue this week or next week.

@NiwakaDev
Copy link
Collaborator

I guess this issue should be blocked on #196.

@chinedufn
Copy link
Owner

I don't think it needs to be blocked.
We can support swift_repr = "struct" for now, and save swift_repr = "class" for later.

@NiwakaDev
Copy link
Collaborator

NiwakaDev commented Mar 27, 2023

I don't think it needs to be blocked.
We can support swift_repr = "struct" for now, and save swift_repr = "class" for later.

Ok.

By the way, I said that I'd like to work on this issue, but I'll leave it to a new contributor or someone else.

@NiwakaDev
Copy link
Collaborator

Right now, I personally need this feature, so I have decided to implement it.

@chinedufn
Copy link
Owner

Nice! Sounds great. That will let us finally handle this TODO

// TODO: Return a `Result<MyIpAddress, SomeErrorType>`
// Once we support returning Result from an async function.
async fn get_my_ip_from_rust() -> ffi::MyIpAddress {
println!("Starting HTTP request from the Rust side...");
let origin = reqwest::get("https://httpbin.org/ip")
.await
.unwrap()
.json::<HashMap<String, String>>()
.await
.unwrap()
.remove("origin")
.unwrap();
println!("HTTP request complete. Returning the value to Swift...");
ffi::MyIpAddress { origin }
}

@NiwakaDev
Copy link
Collaborator

NiwakaDev commented Apr 22, 2023

I found that returning Result<transparent struct, transparent struct> works correctly without needing any PRs.

But, I think that we need to add any tests for returning Result<transparent struct, transparent struct>.

I guess that the following test codes might be helpful as references.

Codegen Test:

/// Test code generation for Rust function that returns a Result<T, E> where T is a transparent enum type and
/// E is a opaque Rust type.
mod extern_rust_fn_return_result_transparent_enum_type_and_opaque_rust_type {
use super::*;
fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
enum SomeOkEnum {
Variant1,
Variant2(i32),
}
extern "Rust" {
type SomeErrType;
}
extern "Rust" {
fn some_function() -> Result<SomeOkEnum, SomeErrType>;
}
}
}
}
fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[repr(C)]
pub enum ResultSomeOkEnumAndSomeErrType{
Ok(__swift_bridge__SomeOkEnum),
Err(*mut super::SomeErrType),
}
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function() -> ResultSomeOkEnumAndSomeErrType{
match super::some_function() {
Ok(ok) => ResultSomeOkEnumAndSomeErrType::Ok(ok.into_ffi_repr()),
Err(err) => ResultSomeOkEnumAndSomeErrType::Err(Box::into_raw(Box::new({
let val: super::SomeErrType = err;
val
})) as *mut super::SomeErrType),
}
}
})
}
fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
public func some_function() throws -> SomeOkEnum {
try { let val = __swift_bridge__$some_function(); switch val.tag { case __swift_bridge__$ResultSomeOkEnumAndSomeErrType$ResultOk: return val.payload.ok.intoSwiftRepr() case __swift_bridge__$ResultSomeOkEnumAndSomeErrType$ResultErr: throw SomeErrType(ptr: val.payload.err) default: fatalError() } }()
}
"#,
)
}
fn expected_c_header() -> ExpectedCHeader {
ExpectedCHeader::ContainsManyAfterTrim(vec![
r#"
typedef enum __swift_bridge__$ResultSomeOkEnumAndSomeErrType$Tag {__swift_bridge__$ResultSomeOkEnumAndSomeErrType$ResultOk, __swift_bridge__$ResultSomeOkEnumAndSomeErrType$ResultErr} __swift_bridge__$ResultSomeOkEnumAndSomeErrType$Tag;
union __swift_bridge__$ResultSomeOkEnumAndSomeErrType$Fields {struct __swift_bridge__$SomeOkEnum ok; void* err;};
typedef struct __swift_bridge__$ResultSomeOkEnumAndSomeErrType{__swift_bridge__$ResultSomeOkEnumAndSomeErrType$Tag tag; union __swift_bridge__$ResultSomeOkEnumAndSomeErrType$Fields payload;} __swift_bridge__$ResultSomeOkEnumAndSomeErrType;
"#,
r#"struct __swift_bridge__$ResultSomeOkEnumAndSomeErrType __swift_bridge__$some_function(void)"#,
])
}
#[test]
fn extern_rust_result_transparent_enum_type_and_opaque_rust_type() {
CodegenTest {
bridge_module: bridge_module_tokens().into(),
expected_rust_tokens: expected_rust_tokens(),
expected_swift_code: expected_swift_code(),
expected_c_header: expected_c_header(),
}
.test();
}
}

Integration Test:

/// Verify that we can receive a Result<TransparentEnum, OpaqueRust> from Rust
func testResultTransparentEnumOpaqueRust() throws {
XCTContext.runActivity(named: "Should return a ResultTestOpaqueRustType") {
_ in
do {
let resultTransparentEnum : ResultTransparentEnum = try rust_func_return_result_transparent_enum_opaque_rust(true)
switch resultTransparentEnum {
case .NamedField(let data):
XCTAssertEqual(data, 123)
case .UnnamedFields(_, _):
XCTFail()
case .NoFields:
XCTFail()
}
} catch {
XCTFail()
}
}
XCTContext.runActivity(named: "Should throw an error") {
_ in
do {
let _: ResultTransparentEnum = try rust_func_return_result_transparent_enum_opaque_rust(false)
XCTFail("The function should have returned an error.")
} catch _ as ResultTestOpaqueRustType {
} catch {
XCTFail()
}
}
}

And then we need to update examples/async-functions/src/lib.rs:

// TODO: Return a `Result<MyIpAddress, SomeErrorType>`
// Once we support returning Result from an async function.

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

3 participants