chinedufn / swift-bridge

swift-bridge facilitates Rust and Swift interop.
https://chinedufn.github.io/swift-bridge
Apache License 2.0
801 stars 57 forks source link

Are generics possible? #40

Closed cozyGalvinism closed 2 years ago

cozyGalvinism commented 2 years ago

I have the following struct type defined currently APIResponseObject<T, MT>, where both T and MT are supposed to be deserialized by Rust.

For some reason, if I try to extern them, I get a weird error, probably because generics (e.g. MyStruct<T>) aren't implemented for the bridge yet?

Following code is my bridge:

#[swift_bridge::bridge]
mod ffi {
    // inferred from https://chinedufn.github.io/swift-bridge/bridge-module/opaque-types/index.html
    extern "Rust" {
        type APIResponseList<T, MT>;
        type PteroClientServer;
        type APIResponsePaginationMetaData;
        type APIResponseObject<T, MT>;
    }

    extern "Rust" {
        type PteroClient;

        #[swift_bridge(init)]
        fn new(base_url: String, api_key: String) -> PteroClient;

        // inferred from https://github.com/chinedufn/swift-bridge/blob/ecc6704985ea260ae502d7ccdd776602e5f263ea/examples/async-functions/src/lib.rs
        async fn get_servers(&self) -> Option<APIResponseList<APIResponseObject<PteroClientServer, APIResponsePaginationMetaData>, APIResponsePaginationMetaData>>;
    }
}

The error I receive:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error("expected `,`")', /home/cozygalvinism/.cargo/registry/src/github.com-1ecc6299db9ec823/swift-bridge-ir-0.1.29/src/bridged_type.rs:335:82

Am I doing something wrong or is my assumption correct?

chinedufn commented 2 years ago

You're right that the issue is because we don't currently support generics.


Would something like this meet your needs for now?

type MyApiResponse = APIResponseList<
    APIResponseObject<PteroClientServer, APIResponsePaginationMetaData>,
    APIResponsePaginationMetaData,
>;

#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        type MyApiResponse;
    }

    extern "Rust" {
        type PteroClient;

        #[swift_bridge(init)]
        fn new(base_url: String, api_key: String) -> PteroClient;

        // inferred from https://github.com/chinedufn/swift-bridge/blob/ecc6704985ea260ae502d7ccdd776602e5f263ea/examples/async-functions/src/lib.rs
        async fn get_servers(&self) -> Option<MyApiResponse>;
    }
}

Not as flexible as generics, but could work fine in the meantime if you don't have a lot of combinations that you need to support.

cozyGalvinism commented 2 years ago

I actually tried that and it still didn't work. However, this may be because async also isn't working for me, like, in general. If I clone your repo, I can build the example just fine, however in my project, async just results in thread 'main' panicked at 'not implemented', /home/cozygalvinism/.cargo/registry/src/github.com-1ecc6299db9ec823/swift-bridge-ir-0.1.29/src/bridged_type.rs:695:25.

I spent the last few hours converting my generics to flat classes, to no avail, then I wrote a few macros that define structs twice: once with serde support and another time without with a From implementation (because I thought it could've been serde's macros) but that also didn't work.

So right now, my entire code looks like:

pub struct MyAsyncClient;

impl MyAsyncClient {
    async fn run_something(&self) -> String;
}

#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        type MyAsyncClient;

        async fn run_something(&self) -> String;
    }
}

with my Cargo.toml looking like:

[package]
name = "ptero-rs"
version = "0.1.0"
edition = "2021"
build = "build.rs"

[lib]
name = "ptero_rs"
crate-type = ["staticlib"]

[dependencies]
reqwest = { version = "0.11.10", features = ["json"] }
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.79"
swift-bridge = { version = "0.1", features = ["async"] }

[build-dependencies]
swift-bridge-build = "0.1"

I'm probably missing something obvious... And if so, I'm sorry for bothering you x)

cozyGalvinism commented 2 years ago

I think I found out why: In my example, I try to run an async function that's implemented for a struct. If I just make it an unbound async function, it compiles fine. I will unbind my async functions tomorrow and test if that works with the type solution you proposed.

If this is intended for now (as in: it will be possible to use bound async methods in the future), I will make adjustments to my library and call it done until this crate has implemented support for it c: I also cannot thank you enough for working on this crate, truly amazing job!

chinedufn commented 2 years ago

Thanks for the detailed report. And thanks a lot for the kind words.

Right now we only support async functions that have 0 arguments. We also don't support async functions that take a receiver (i.e. &self).

Not including arguments made it easier to get a first version out.

This does mean that what you're trying to do is more or less impossible to express with swift-bridge right now.

I'd be more than happy to guide you on how to add support for &self in async functions to swift-bridge if you like. Otherwise, I can see if I can possibly implement this this week. But week ahead so I'm not entirely sure.

cozyGalvinism commented 2 years ago

Thanks for the detailed report. And thanks a lot for the kind words.

Right now we only support async functions that have 0 arguments. We also don't support async functions that take a receiver (i.e. &self).

Not including arguments made it easier to get a first version out.

This does mean that what you're trying to do is more or less impossible to express with swift-bridge right now.

I'd be more than happy to guide you on how to add support for &self in async functions to swift-bridge if you like. Otherwise, I can see if I can possibly implement this this week. But week ahead so I'm not entirely sure.

Implementing this sounds good but even if that doesn't work out, I will implement this using a blocking client in the meantime (async seems like more trouble than it's worth for now). Big reason why I wanted to use this crate was because I already had the request and response models done in Rust, so I didn't want to do them again in Swift but rather just writing the client in Rust and then using that client in Swift (and because I'm rather new to Swift).

Or I just do it in Swift for the learning experience.

chinedufn commented 2 years ago

I thought of a way to implement support for generics.

I can implement generics and async functions that have arguments. Then you should be good to go.

Just not sure if I'll get to it this week.

Did you manage to find a path forward in the meantime?

cozyGalvinism commented 2 years ago

Sort of? I am using reqwest::blocking rn and that builds just fine (as expected). I will re-implement my generics now and see if that works too.

EDIT: Yup, confirmed the following as working:

type GetServersResponseList = models::responses::APIResponseList<models::responses::APIResponseObject<models::responses::PteroClientServer, models::responses::APIResponsePaginationMetaData>, models::responses::APIResponsePaginationMetaData>;

#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        type GetServersResponseList;
    }

    extern "Rust" {
        type PteroClient;

        #[swift_bridge(init)]
        fn new(base_url: String, api_key: String) -> PteroClient;

        fn get_servers(&self) -> Option<GetServersResponseList>;
    }
}
chinedufn commented 2 years ago

Async Rust functions and methods are now supported

https://github.com/chinedufn/swift-bridge/releases/tag/0.1.30

cozyGalvinism commented 2 years ago

Oh wow! I will test this once I have a bit more time on my hands c: Thank you very much!

chinedufn commented 2 years ago

No problem!

Your use case would still be a bit painful until generics are supported.

Feel free to subscribe to https://github.com/chinedufn/swift-bridge/issues/44 which tracks generics support.

chinedufn commented 2 years ago

Closing in favor of https://github.com/chinedufn/swift-bridge/issues/44