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

Design question about using a single bridge file vs. in-line annotations #300

Closed
ryankaplan opened this issue Nov 12, 2024 · 8 comments
Closed

Comments

@ryankaplan
Copy link

ryankaplan commented Nov 12, 2024

Hi! First off, thanks for creating Swift-Bridge! I really appreciate your work!

I have a question about the API design for the project. Swift-Bridge requires users to define a #[swift_bridge::bridge] module to expose functionality, whereas projects like wasm-bindgen allow attributes to be added directly to structs, enums, or functions throughout the codebase.

I’m curious about the reasoning behind this design choice. Is there a technical limitation that prevented you from annotating types in-line? Or did you prefer to have all bindings in a single file?

The reason I ask is that I I have many types throughout my Rust codebase that are exposed transparently to wasm/JS and that I'd like to expose to Swift. They currently live next to the rust code that handles them; I think it'd hurt codebase organization to put them in a single bridge file. I wonder if you've thought about this use case and have any advice.

Looking forward to hearing your thoughts! Thanks again for the amazing work.

Update: I see that maybe the API of swift-bridge is modeled after cxx. If any case I'm curious to know how you feel about the trade-offs between the single-file bridge API vs. inline annotations.

@chinedufn
Copy link
Owner

Hey Ryan.

I've added documentation explaining why we support the bridge module approach: #301

In short, separating bindings from types was a matter of preference.
It also tends towards more efficient code, but for most real-world use cases I'd expect the difference to be meaningless.
All of this is better explained in the new documentation.


Please note that it is currently possible to define your FFI boundary across multiple files.

You can use the #[swift_bridge(already_declared)] attribute.

## Opaque Type Attributes
#### #[swift_bridge(already_declared)]
The `already_declared` attribute allows you to use the same type in multiple bridge modules.
```rust
use some_crate::App;
mod ffi {
extern "Rust" {
type App;
#[swift_bridge(init)]
fn new() -> App;
}
}
#[swift_bridge::bridge]
#[cfg(feature = "dev-utils")]
mod ffi_dev_utils {
extern "Rust" {
// We won't emit Swift and C type definitions for this type
// since we've already declared it elsewhere.
#[swift_bridge(already_declared)]
type App;
fn create_logged_in_user(&mut self, user_id: u8);
}
}
```

Does that help with your use case?

@chinedufn
Copy link
Owner

Here's an example of using multiple bridge modules: #302

@chinedufn
Copy link
Owner

Please re-open if you have more questions.

@ryankaplan
Copy link
Author

Ooh interesting, thank you! Really appreciated reading through the documentation, and while I'd love inline annotations multiple bridge files works fine for me.

@ryankaplan
Copy link
Author

Ah ok, I am running into a new issue as a result of this design choice and I'm curious if you have any advice! I'm building an app with a Rust engine that has both a Swift and a web JS frontend. I'm using swift-bridge to bind to Swift, and wasm_bindgen to bind to JS on web.

I have many enums and POD-types declared in Rust that I'd like to expose to Swift. But right now they're scattered throughout my app. I'm happy to move them to the bridge file, but when I do they'll no longer be exposed to web.

I can work around this for structs by declaring them all as opaque and declaring getters as necessary. But it looks like the only way to declare enums with swift bridge is transparently. This means that I need to create duplicate "SwiftThing" enums in my bridge file, and convert them to rust types when going back and forth.

One fix I could imagine is being able to declare enum Thing; in the swift bridge file, referencing an existing Rust enum, and to generate a swift equivalent for it.

Thanks again for the input so far! And for building swift-bridge! Other than this it's been really smooth to use.

@ryankaplan
Copy link
Author

Ah, I've discovered args_into and return_into. So I can declare transparent enums like FfiEnum and then automate the conversions. Is that the intended happy path?

@chinedufn
Copy link
Owner

Yeah that's one way that args_into and return_into can be used.

For some use cases you could also consider use ffi::MyEnum so that you don't have to duplicate the enum.
I don't personally do that since my application code doesn't know about my bridging layer, but I've seen other open source crates use that pattern.

@chinedufn
Copy link
Owner

Oh I just saw your first comment #300 (comment)

Specifically:

I'm happy to move them to the bridge file, but when I do they'll no longer be exposed to web.

So, yeah, args_into and return_into are currently the best way to handle your problem.

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

No branches or pull requests

2 participants