What is swift-bridge?
masterswift-bridge is a tool that generates bindings to enable bidirectional communication between Rust and Swift. It allows you to call Rust code from Swift and Swift code from Rust.repository·master·Indexed 22 days ago
https://github.com/chinedufn/swift-bridgeA library for generating FFI bindings to enable safe, high-level interop between Rust and Swift. It supports sharing complex types like structs, transparent enums, and generic types, as well as bidirectional async/await function calls. The tool provides a bridge module system using the #[swift_bridge::bridge] macro to export Rust functions to Swift and import Swift functions into Rust, with support for conditional compilation via #[cfg] attributes.
swift-bridge is a tool that generates bindings to enable bidirectional communication between Rust and Swift. It allows you to call Rust code from Swift and Swift code from Rust.When you export a Rust std::vec::Vec through a swift_bridge module, it is represented on the Swift side as a RustVec.
RustVec implements Swift's IteratorProtocol, which allows you to iterate over its elements using standard Swift for-in loops. It also provides common vector operations such as push(), pop(), get(), and len().
let vec: RustVec = get_rust_vec_somehow()
for value in vec {
print(value)
}In swift-bridge, you define your Foreign Function Interface (FFI) by creating a "bridge module" decorated with the #[swift_bridge::bridge] procedural macro. This module acts as the single source of truth for your interface.
extern "Rust" blocks: Used to export Rust types and functions so they can be called from Swift.extern "Swift" blocks: Used to import Swift types and functions so they can be called from Rust.You can include multiple extern "Rust" and extern "Swift" blocks within a single bridge module to declare all necessary types and functions.
#[swift_bridge::bridge]
mod ffi {
// Export Rust types and functions for Swift to use.
extern "Rust" {
type SomeRustType;
fn some_type_method(&mut self) -> String;
}
// Import Swift types and functions for Swift to use.
extern "Swift" {
type SomeSwiftClass;
#[swift_bridge(swift_name = "someClassMethod")]
fn some_class_method(&self, arg: u8);
}
}You can pass Rust's &str types between Rust and Swift using the RustStr type in Swift.
&'static str or &str in a #[swift_bridge::bridge] module will be received as a RustStr in Swift.extern "Swift" and returns a &str, Swift provides the string and Rust receives it as a standard &str.#[swift_bridge::bridge]
mod ffi {
extern "Rust" {
type SomeRustType;
// Becomes a `RustStr` when passed to Swift.
fn make_str() -> &'static str;
fn get_str(self: &SomeRustType) -> &str;
}
extern "Swift" {
type SomeSwiftType;
// Swift returns a `RustStr` and
// Rust receives a `&str`.
fn make_string() -> &str;
}
}Because there is no single compiler that handles both Swift and Rust, you must use a two-step approach to create a final binary. You cannot compile both languages simultaneously in one pass; instead, you must compile one language into a native library first, and then use the second language's compiler to compile the final executable while linking against that native library.
There are two primary directions for this workflow:
libmy_rust_crate.a), then compile the Swift code into a final executable while linking the Rust library.The choice of direction typically depends on your existing build tools and project requirements.
┌──────────────────────────────────┐ ┌───────────────────┐
│// Rust code │ │// Swift Code │
│ │ │ │
│pub extern "C" fn rust_hello() { │ │rust_hello() │
│ println!("Hi, I'm Rust!") │ │ │
│} │ │ │
└──────────────────────────────────┘ └───────────────────┘
│ │
Compile Rust │ │ Compile Swift to
to native lib │ │ executable
│ │
▼ Link in Rust │
┌────────────────────────────────┐ native lib │
│ libmy_rust_crate.a │───────────────────────┤
└────────────────────────────────┘ │
│
▼
┌──────────────────────────────────┐
│ Final Executable Binary │
│ │
└──────────────────────────────────┘You can define structs using the #[swift_bridge(swift_repr = "struct")] attribute within a #[swift_bridge::bridge] module. This allows the struct's fields to be accessible by both Rust and Swift.
Note that Swift structs are copy-on-write; therefore, swift_bridge does not allow mutating the fields of a struct defined with swift_repr = "struct" because mutations would not affect the original instance.
// Rust
#[swift_bridge::bridge]
mod ffi {
#[swift_bridge(swift_repr = "struct")]
struct SomeSharedStruct {
some_field: u8,
another_field: Option<u64>
}
extern "Rust" {
fn some_function(val: SomeSharedStruct);
}
extern "Swift" {
fn another_function() -> SomeSharedStruct;
}
}Because a Rust std::string::String is Send+Sync, the corresponding Swift types RustString, RustStringRef, and RustStringRefMut all implement the Swift Sendable protocol.
These types are thread-safe provided that the Swift code does not violate Rust's ownership and aliasing rules.
swift-bridge allows you to define and generate code for multiple bridge modules across one or more files within a single Rust crate. This is useful for organizing different domains or logical boundaries (e.g., user and bank) into separate bridge modules that are each exposed to Swift independently.swift-bridge allows you to define structs and enums with "Transparent Types." This feature enables the fields of these types to be visible and accessible to both Swift and Rust, facilitating seamless data sharing between the two languages.The swift-bridge allows for direct mapping of Rust tuples (A, B, C, ...) to Swift tuples (A, B, C, ...). This enables passing multiple values as a single argument or return value across the FFI boundary without needing to define explicit structs for simple groupings.
// Rust
mod ffi {
extern "Rust" {
fn get_midpoint(
point1: (f32, f32, f32),
point2: (f32, f32, f32),
) -> (f32, f32, f32);
}
}While swift-bridge provides type safety for all generated FFI code, developers are responsible for maintaining memory safety when interacting between Swift and Rust. Because Swift does not enforce Rust's borrowing and ownership rules at compile time, you must manually follow these three rules to avoid undefined behavior:
RustStr) in Swift after the parent Rust type has been dropped.&mut self if you are simultaneously holding an immutable reference to that same value.drop function), do not attempt to access or drop that value again in Swift.While swift-bridge provides high-level macros, library authors can use the swift-bridge-ir crate directly to build custom code generators. This is useful for creating alternative frontends, such as custom procedural macros that wrap swift-bridge-ir logic to annotate types for Swift exposure.
For example, you could implement a third-party library that provides an attribute macro like #[some_third_party_lib::ExposeToSwift] which internally utilizes swift-bridge-ir to generate the necessary Rust+Swift FFI boundary.
use some_third_party_lib;
/// An imaginary third-party library that wraps `swift-bridge-ir`
/// in a proc macro attribute that users can annotate their types
/// with.
#[some_third_party_lib::ExposeToSwift]
pub struct User {
name: String
}